VMwareで動作するAlmaLinuxで画面解像度を保存する方法
VMware上で動作しているUbuntuやCentOS 7の場合、画面解像度を設定して再起動した場合は、その解像度で起動するのですが、AlmaLinux 8, 9やRocky Linux 8, 9(恐らくCentOS 8, 9 StreamやRHEL8, 9でも同様)では、800x600や1024x768のような低解像で起動してしまいます。
海外のサイトで画面解像度を固定して起動する方法を見つけたのでその手順を紹介します。
上記サイトでは、画面解像度の設定で使用するスクリプトを特定ユーザーのホームディレクトリ内に置いているのですが、本ブログでは、別のディレクトリに変更しています。
手順:
sudoedit /usr/local/bin/gnome-randr.py
下記サイトを開きCopy file contents
とツールチップで表示されるアイコンのクリックでクリップボードにコピーできます。
https://gitlab.com/Oschowa/gnome-randr/-/blob/master/gnome-randr.py
sudoedit /usr/local/bin/resolution.sh
以下の内容にして保存する。
#!/bin/sh python3 /usr/local/bin/gnome-randr.py --output Virtual-1 --mode 1920x1200
1920x1200
の部分は、指定したい解像度にしてください。
Virtual-1
の部分は、gnome-randr.pyを引数なしで実行した際に表示されるassociated physical monitors:
の内容です。VMwareの場合はVirtual-1
になりますが、他の仮想OSソフトウェアでは異なると思います。
sudo chmod +x /usr/local/bin/resolution.sh sudoedit /etc/xdg/autostart/resolution.desktop
以下の内容にして保存する。
[Desktop Entry] Encoding=UTF-8 Exec=/usr/local/bin/resolution.sh Name=resolution Terminal=false OnlyShowIn=GNOME Type=Application StartupNotify=false X-GNOME-Autostart-Phase=Application
リスタートして確認してみてください。
以上です。
Windows 10 VM (VMware Workstation 15 Pro) をWindows 11にアップグレード
仮想マシン(以下VMと略記)のWindows 10 ProにWindows 11 Proをアップグレードインストールしたので、その手順を記載します。 VMware Workstation 15.5.7 Pro(以下VMwareと記載)を使用しています。現時点での最新版は16.2.0なので、1つ前のメジャーバージョンになります。
※自分の使用しているWindowsの言語設定が英語のため、メッセージやキャプチャ画像が英語になっています。適宜日本語に読み替えてください。
インストール用のISOイメージの作成
以下の手順です。
- MicorsoftのサイトからISOイメージを作成するツール(Create Windows 11 Installation Media)をダウンロード。
- ダウンロードした
MediaCreationToolW11.exe
を実行。 - Applicable notices and license termsが表示されるので、
[Accept]
をクリック。 - Language/Edtionの選択画面が表示されるので、
[Next]
をクリック。 ISO file
選択して[Next]
をクリック。- ファイル名(例: Windows 11.iso)と保存場所を指定して
[Save]
をクリック。
インストール前の作業
Windows 11へのアップグレードのシステム要件は以下となっています。 (参考サイト: Microsoft) * プロセッサ: 1GHz, 2コア, 64ビット * メモリ: 4GB * ファームウェア: UEFI, セキュア ブート対応 * グラフィックス カード: DirectX 12 (WDDM 2.0) * TPM 2.0
Windows 10 VMが上記要件を満たすように設定します。
ファームウェア: UEFI
ファームウェアタイプがUEFIかどうかは、[VM]-[Settings...]メニュー ⏵ [Options]タブ-[Advanced]-[Firmware type]を確認します。
ここが[BIOS]
になっている場合は、[UEFI]
に変更する必要がありますが、その前にいったんWindows 10を起動して起動ディスクのパーティションタイプを確認します。
パーティションタイプが[MBR]
の場合は、[GPT]
に変更する必要があります。変換しないとUEFIに変更後のWindowsの起動に失敗します。
起動ディスクのパーティションタイプの確認方法
デスクトップまたはエクスプローラの[This PC/コンテキストメニュー]-[Manage] ⏵ [Storage/Disk Management]
[Disk0/コンテキストメニュー]-[Properties] ⏵ [Volumes]タブ (下記はパーティションタイプがMBR)
起動ディスクのパーティションタイプを[GPT]
に変更するには、コマンドプロンプトを管理者で起動して以下を実行します。
C:\WINDOWS\system32>MBR2GPT /convert /allowFullOS MBR2GPT will now attempt to convert the default book disk. If conversion is successful the disk can only be booted in GPT mode. These changes cannot be undone! MBR2GPT: Attempting to convert disk 0 MBR2GPT: Retrieving layout of disk MBR2GPT: Validating layout, disk sector size is: 512 bytes MBR2GPT: Trying to shrink the OS partition MBR2GPT: Creating the EFI system partition MBR2GPT: Installing the new boot files MBR2GPT: Performing the layout conversion MBR2GPT: Migrating default boot entry MBR2GPT: Adding recovery boot entry MBR2GPT: Fixing drive letter mapping MBR2GPT: Conversion completed successfully Call WinReReapir to repair WinRE MBR2GPT: Failed to update ReAgent.xml, please try to manually disable and enable WinRE. MBR2GPT: Before the new system can boot properly you need to switch the firmware to boot to UEFI mode!
Failed to update ReAgent.xmlと表示されていますが、パーティションタイプの変換自体は成功しています。
この後Widnowsをシャットダウンして[VM]-[Settings...]で、[Firmware type]を[UEFI]
に変更します。
[Secure boot]
にチェックを入れる必要はありません。
TPM 2.0
TPM 2.0対応するためには、[VM]-[Settings...]の[Hardware]タブで[Add...]をクリックして [Trusted Platform Module]を追加しますが、その前にVMを暗号化する必要があります。 [Access Control]-[Encrypt...]をクリックして、パスワードを入力して[Encrypt]をクリックします。
暗号化ができたら[Trusted Platform Module]を追加します。
Windows 11のインストール
CD/DVDにWindows 11.isoを割り当ててWindows 10を起動し、DVD Driveのsetup.exeを実行すればWindows 11のインストールが開始されます。
インストール後の作業
ディスクサイズの最小化
Windows 11をインストールすると以前のWindows 10は、C:\Windows.oldに退避されます。Windows 10に戻す事がないのであれば不要なので以下の手順で削除します。
エクスプローラでCドライブのコンテキストメニューから[Properties]を選択し、[Disk Cleanup]をクリック。
[Clean up system files]をクリック。
すべてにチェックを入れ[OK]をクリック。
その後Widnows 11をシャットダウンし、[VM]-[Settings...] ⏵ [Hard Disk]-[Defragment]をクリック。
[Compact]をクリック。
これで起動ディスクに割り当てられたvmdkファイルのサイズを小さくする事ができます。
TPMハードウエアの削除との暗号化の解除
Windows 11をインストールした後はTPMは不要なので削除します。その後暗号化を解除します。
VMwareでVMを開く度にパスワードを入力するのが面倒でなければそのままでもかまいません。
以上です。
DebugTrace-net チュートリアル
DebugTraceについて
DebugTrace-javaチュートリアルを参照ください。
プロジェクト(C#)の作成
Visual Studio 2019(日本語サイト)を使用するのでインストールします。 Visual Studio 2019の言語が日本語の場合は、以下の内容を適宜日本語に置き換えてください。
- Visual Studio 2019を起動後に
Create a new project
をクリックします。(または[File]-[New]-[Project...]
を実行) - Create a new Projectダイアログが表示されるので、
Class Libraey (.NET Core)
を選択してNext
をクリックします。 - Configure your new projectダイアログが表示されるので、以下を入力して
Create
をクリックするとプロジェクトが作成されます。
Project name:Tutorial
Location: <任意>
Solution name:DebugTraceNetTutorial
▢ Place solution and project in the same directory - Solution ExplolerでTutorialプロジェクトのDependenciesのコンテキストメニュー(右ボタンでクリック)から
Manage Nuget Packeges...
を選択します。 - 生成されたソース(DebugTraceNetTutorial/Tutorial/
Class1.cs
)を削除し、Tutorialプロジェクト下にsrc
フォルダーを作成します。 [File]-[New]-[Project...]
を実行します。- Create a new Projectダイアログが表示されるので、
MSTest Test Project (.NET Core)
を選択してNext
をクリックします。 - Configure your new projectダイアログが表示されるので、以下を入力して
Create
をクリックするとテストプロジェクトが追加されます。
Project name:TutorialTest
Location: <変更なし>
Solution:Add to solution
Solution Name: <入力不可> - 生成されたソース(DebugTraceNetTutorial/TutorialTest/
UnitTest1.cs
)を削除し、TutorialTestプロジェクト下にsrc
フォルダーを作成します。 - TutorialTest/Dependenciesのコンテキストメニューから
Add Project Reference...
を選択し、Tutorialプロジェクトにチェックを入れ[OK]
をクリックします。
チュートリアル1
Tutorial/src下に以下のソースファイルを追加します。
パス: DebugTraceNetTutorial\Tutorial\src\Tutorial1.cs
using System.Text; namespace DebugTraceTutorial { public class Tutorial1 { public static int MaxLogByteArrayLength = 1024; /// <summary> /// バイト配列の文字列表現をStringBuilderに追加する。 /// </summary> /// <param name="builder">StringBuilder</param> /// <param name="bytes">バイト配列</param> /// <returns>引数のbuilder</returns> public static StringBuilder AppendBytes(StringBuilder builder, byte[] bytes) { builder.Append('['); var delimiter = ""; for (var index = 0; index < bytes.Length; ++index) { builder.Append(delimiter); if (index >= MaxLogByteArrayLength) { builder.Append("..."); break; } var value = bytes[index]; var ch = (char)(value / 16 + '0'); if (ch >= '9') ch += (char)('A' - '9' - 1); builder.Append(ch); ch = (char)(value % 16 + '0'); if (ch >= '9') ch += (char)('A' - '9' - 1); builder.Append(ch); delimiter = ", "; } builder.Append(']'); return builder; } } }
TutorialTest/src下に以下のソースファイルを追加します。
パス: DebugTraceNetTutorial\TutorialTest\src\Tutorial1Test.cs
using DebugTraceTutorial; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Text; namespace DebugtraceTutorial { [TestClass] public class Tutorial1Test { /** * appendBytesのテスト。 * @param bytes バイト配列 * @param expected StringBuilderに追加されると期待する文字列 */ [DataTestMethod] [DataRow(new byte[] {}, "[]")] [DataRow(new byte[] {1}, "[01]")] [DataRow( new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, "[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F]" )] [DataRow( new byte[] {0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}, "[F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, FA, FB, FC, FD, FE, FF]" )] public void TestAppendBytes(byte[] bytes, string expected) { // when var builder = Tutorial1.AppendBytes(new StringBuilder(), bytes); // then Assert.AreEqual(expected, builder.ToString()); } } }
テストを実行すると最初の2つの引数のテストは成功しますが、その後のテストでは失敗します。
テスト結果(最初の失敗):
Assert.AreEqual failed. Expected:<[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F]>. Actual:<[00, 01, 02, 03, 04, 05, 06, 07, 08, 0@, 0A, 0B, 0C, 0D, 0E, 0F]>.
08, 0@, 0A
ではなく08, 09, 0A
となるのが正しいです。
DebugTrace-netを使用してみます。
プロジェクトにDebugTrace-netパッケージを以下の手順で追加します。
1. Solution Explorer
のTutorial/Dependecies
のコンテキストメニューからManage NuGet Package...
を選択します。
1. Brawse
をクリックし、DebugTrace
で検索して表示されたリストからDebugTrace
を選択してインストールします。
対象のメソッドとテストメソッドにDebugTrace-netのメソッドを呼び出すコードを挿入します。
using System.Text; using static DebugTrace.CSharp; // TODO: Delete after debugging namespace DebugTraceTutorial { public class Tutorial1 { public static int MaxLogByteArrayLength = 1024; /// <summary> /// バイト配列の文字列表現をStringBuilderに追加する。 /// </summary> /// <param name="builder">StringBuilder</param> /// <param name="bytes">バイト配列</param> /// <returns>引数のbuilder</returns> public static StringBuilder AppendBytes(StringBuilder builder, byte[] bytes) { Trace.Enter(); // TODO: Delete after debugging Trace.Print("bytes", bytes); // TODO: Delete after debugging builder.Append('['); var delimiter = ""; for (var index = 0; index < bytes.Length; ++index) { Trace.Print("index", index); // TODO: Delete after debugging builder.Append(delimiter); if (index >= MaxLogByteArrayLength) { builder.Append("..."); break; } var value = bytes[index]; Trace.Print("value", value); // TODO: Delete after debugging var ch = (char)(value / 16 + '0'); Trace.Print("1-1 ch", ch); // TODO: Delete after debugging if (ch >= '9') ch += (char)('A' - '9' - 1); Trace.Print("1-2 ch", ch); // TODO: Delete after debugging builder.Append(ch); ch = (char)(value % 16 + '0'); Trace.Print("2-1 ch", ch); // TODO: Delete after debugging if (ch >= '9') ch += (char)('A' - '9' - 1); Trace.Print("2-2 ch", ch); // TODO: Delete after debugging builder.Append(ch); delimiter = ", "; } builder.Append(']'); Trace.Leave(); // TODO: Delete after debugging return builder; } } }
using DebugTraceTutorial; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Text; using static DebugTrace.CSharp; // TODO: Delete after debugging namespace DebugtraceTutorial { [TestClass] public class Tutorial1Test { /** * appendBytesのテスト。 * @param bytes バイト配列 * @param expected StringBuilderに追加されると期待する文字列 */ [DataTestMethod] [DataRow(new byte[] {}, "[]")] [DataRow(new byte[] {1}, "[01]")] [DataRow( new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, "[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F]" )] [DataRow( new byte[] {0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}, "[F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, FA, FB, FC, FD, FE, FF]" )] public void TestAppendBytes(byte[] bytes, string expected) { Trace.Enter(); // TODO: Delete after debugging Trace.Print("bytes", bytes); // TODO: Delete after debugging Trace.Print("expected", expected); // TODO: Delete after debugging // when var builder = Tutorial1.AppendBytes(new StringBuilder(), bytes); // then Assert.AreEqual(expected, builder.ToString()); Trace.Leave(); // TODO: Delete after debugging } } }
もう一度テストを実行してみます。以下は上記を実行したログの一部です。ログはデフォルトで標準エラー(stderr)に出力されます。
Text Explorer
のTest Detail Summary
からOpen additional output for this result
をクリッすると出力内容を確認できます。
2020-11-22 12:03:32.798 [04] Enter DebugtraceTutorial.Tutorial1Test.TestAppendBytes(Byte[] bytes, String expected) (Tutorial1Test.cs:28) 2020-11-22 12:03:32.799 [04] | 2020-11-22 12:03:32.799 [04] | bytes = 2020-11-22 12:03:32.799 [04] | byte[16] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} (Tutorial1Test.cs:29) 2020-11-22 12:03:32.799 [04] | 2020-11-22 12:03:32.799 [04] | expected = 2020-11-22 12:03:32.799 [04] | (Length:64)"[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0 2020-11-22 12:03:32.799 [04] | E, 0F]" (Tutorial1Test.cs:30) 2020-11-22 12:03:32.800 [04] | Enter DebugTraceTutorial.Tutorial1.AppendBytes(StringBuilder builder, Byte[] bytes) (Tutorial1.cs:17) 2020-11-22 12:03:32.800 [04] | | 2020-11-22 12:03:32.800 [04] | | bytes = 2020-11-22 12:03:32.800 [04] | | byte[16] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} (Tutorial1.cs:18) 2020-11-22 12:03:32.800 [04] | | 2020-11-22 12:03:32.800 [04] | | index = 0 (Tutorial1.cs:22) 2020-11-22 12:03:32.800 [04] | | value = byte 0 (Tutorial1.cs:29) 2020-11-22 12:03:32.801 [04] | | 1-1 ch = '0' (Tutorial1.cs:31) 2020-11-22 12:03:32.801 [04] | | 1-2 ch = '0' (Tutorial1.cs:33) 2020-11-22 12:03:32.801 [04] | | 2-1 ch = '0' (Tutorial1.cs:36) 2020-11-22 12:03:32.802 [04] | | 2-2 ch = '0' (Tutorial1.cs:38) ... 2020-11-22 12:03:32.822 [04] | | index = 9 (Tutorial1.cs:22) 2020-11-22 12:03:32.822 [04] | | value = byte 9 (Tutorial1.cs:29) 2020-11-22 12:03:32.822 [04] | | 1-1 ch = '0' (Tutorial1.cs:31) 2020-11-22 12:03:32.824 [04] | | 1-2 ch = '0' (Tutorial1.cs:33) 2020-11-22 12:03:32.825 [04] | | 2-1 ch = '9' (Tutorial1.cs:36) 2020-11-22 12:03:32.826 [04] | | 2-2 ch = '@' (Tutorial1.cs:38) ...
バイト配列の9
の2桁目が'9'
であるはずが'@'
に間違って変換されています。
これはif (ch >= '9')
の判定が間違っているのでif (ch > '9')
に修正します。
修正後もう一度テストを実行すると成功します。ログの一部は以下になります。
2020-11-22 12:04:41.466 [05] | | index = 9 (Tutorial1.cs:22) 2020-11-22 12:04:41.466 [05] | | value = byte 9 (Tutorial1.cs:29) 2020-11-22 12:04:41.466 [05] | | 1-1 ch = '0' (Tutorial1.cs:31) 2020-11-22 12:04:41.468 [05] | | 1-2 ch = '0' (Tutorial1.cs:33) 2020-11-22 12:04:41.468 [05] | | 2-1 ch = '9' (Tutorial1.cs:36) 2020-11-22 12:04:41.468 [05] | | 2-2 ch = '9' (Tutorial1.cs:38)
デバッグの終了後にデバッグ用コードを削除するには、正規表現で検索して空文字列に置換します。改行コードも削除しているのでデバッグ用コードを挿入する前のソースに戻ります。
正規表現検索文字列: ^.+(Debug)?Trace\..*\r?\n
チュートリアル2
次のチュートリアルのソースとテストソースは以下です。
パス: DebugTraceNetTutorial\Tutorial\src\Tutorial2.cs
using System; using System.Collections.Generic; using System.Text; namespace DebugTraceTutorial { public class Tutorial2 { private static Dictionary<Type, string> shortNames = new Dictionary<Type, string>() { {typeof(void) , "void" }, {typeof(bool) , "bool" }, {typeof(sbyte) , "sbyte" }, {typeof(byte) , "byte" }, {typeof(short) , "short" }, {typeof(ushort), "ushort"}, {typeof(int) , "int" }, {typeof(uint) , "uint" }, {typeof(long) , "long" }, {typeof(ulong) , "ulong" }, {typeof(float) , "float" }, {typeof(double), "double"}, {typeof(decimal), "decimal"}, {typeof(char) , "char" }, {typeof(string), "string"}, }; /// <summary> /// 型名を返します。ただしSystem名前空間の場合は名前空間を取り除いて返します。 /// </summary> /// <param name="type">型</param> /// <returns>型名</returns> public static string GetName(Type type) { if (shortNames.ContainsKey(type)) return shortNames[type]; if (type.IsArray) return GetName(type.GetElementType()) + "[]"; var typeArguments = type.GenericTypeArguments; var backets = ("", ""); var isValueTuple = type == typeof(ValueTuple); if (typeArguments.Length > 0) backets = isValueTuple ? ("(", ")") : ("<", ">"); var builder = new StringBuilder(); if (!isValueTuple) { var typeName = type.Namespace == "System" ? type.Name : type.FullName; var backquoteIndex = typeName.IndexOf('`'); if (backquoteIndex >= 0) typeName = typeName.Substring(0, backquoteIndex); builder.Append(typeName); } builder.Append(backets.Item1); var delimiter = ""; foreach (var typeArgument in typeArguments) { builder.Append(delimiter).Append(GetName(typeArgument)); delimiter = ", "; } builder.Append(backets.Item2); return builder.ToString(); } } }
パス: DebugTraceNetTutorial\TutorialTest\src\Tutorial2Test.cs
using DebugTraceTutorial; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Data; namespace DebugtraceTutorial { [TestClass] public class Tutorial2Test { /** * GetNameのテスト。 * @param type 対象の型 * @param expected 予期する型名 */ [DataTestMethod] /* 1 */ [DataRow(typeof(string), "string")] /* 2 */ [DataRow(typeof(string[]), "string[]")] /* 3 */ [DataRow(typeof(string[][]), "string[][]")] /* 4 */ [DataRow(typeof(List<string>), "System.Collections.Generic.List<string>")] /* 5 */ [DataRow(typeof(List<string>[]), "System.Collections.Generic.List<string>[]")] /* 6 */ [DataRow(typeof((int, long)), "(int, long)")] /* 7 */ [DataRow(typeof(((byte, short), (int, long))), "((byte, short), (int, long))")] public void TestGetName(Type type, string expected) { // when var typeName = Tutorial2.GetName(type); // then Assert.AreEqual(expected, typeName); } } }
実行すると最初の5つで成功しますが、後の2つ(ValueTuple)では失敗します。
テスト結果(最初の失敗):
Assert.AreEqual failed. Expected:<(int, long)>. Actual:<ValueTuple<int, long>>.
デバッグコードを挿入しますが、メソッドが終了する箇所が3箇所あるため、Trace.Leave
メソッドの呼び出しをGetName
メソッドの最後に挿入だけでは不十分です。
この場合は、以下のようにします。
using System; using System.Collections.Generic; using System.Text; using static DebugTrace.CSharp; // TODO: Delete after debugging namespace DebugTraceTutorial { public class Tutorial2 { private static Dictionary<Type, string> shortNames = new Dictionary<Type, string>() { {typeof(void) , "void" }, {typeof(bool) , "bool" }, {typeof(sbyte) , "sbyte" }, {typeof(byte) , "byte" }, {typeof(short) , "short" }, {typeof(ushort), "ushort"}, {typeof(int) , "int" }, {typeof(uint) , "uint" }, {typeof(long) , "long" }, {typeof(ulong) , "ulong" }, {typeof(float) , "float" }, {typeof(double), "double"}, {typeof(decimal), "decimal"}, {typeof(char) , "char" }, {typeof(string), "string"}, }; /// <summary> /// 型名を返します。ただしSystem名前空間の場合は名前空間を取り除いて返します。 /// </summary> /// <param name="type">型</param> /// <returns>型名</returns> public static string GetName(Type type) { try {Trace.Enter(); // TODO: Delete after debugging Trace.Print("type", type); // TODO: Delete after debugging if (shortNames.ContainsKey(type)) return shortNames[type]; if (type.IsArray) return GetName(type.GetElementType()) + "[]"; var typeArguments = type.GenericTypeArguments; Trace.Print("typeArguments", typeArguments); // TODO: Delete after debugging var backets = ("", ""); var isValueTuple = type == typeof(ValueTuple); Trace.Print("typeof(ValueTuple)", typeof(ValueTuple)); // TODO: Delete after debugging Trace.Print("isValueTuple", isValueTuple); // TODO: Delete after debugging if (typeArguments.Length > 0) backets = isValueTuple ? ("(", ")") : ("<", ">"); Trace.Print("backets", backets); // TODO: Delete after debugging var builder = new StringBuilder(); if (!isValueTuple) { var typeName = type.Namespace == "System" ? type.Name : type.FullName; var backquoteIndex = typeName.IndexOf('`'); if (backquoteIndex >= 0) typeName = typeName.Substring(0, backquoteIndex); builder.Append(typeName); } builder.Append(backets.Item1); var delimiter = ""; foreach (var typeArgument in typeArguments) { builder.Append(delimiter).Append(GetName(typeArgument)); delimiter = ", "; } builder.Append(backets.Item2); return builder.ToString(); } finally {Trace.Leave();} // TODO: Delete after debugging } } }
テスト対象のメソッド全体をtry {Trace.Enter();
と} finally {Trace.Leave();}
で囲います。
テストメソッドの方はチュートリアル1と同様にします。
テストを実行すると以下のログ(失敗している部分)が出力されます。
2020-11-22 19:05:52.115 [04] Enter DebugtraceTutorial.Tutorial2Test.TestGetName(Type type, String expected) (Tutorial2Test.cs:27) 2020-11-22 19:05:52.115 [04] | 2020-11-22 19:05:52.115 [04] | type = 2020-11-22 19:05:52.115 [04] | System.RuntimeType System.ValueTuple`2[System.Int32,System.Int64] (Tutorial2Test.cs:28) 2020-11-22 19:05:52.115 [04] | 2020-11-22 19:05:52.115 [04] | expected = (Length:11)"(int, long)" (Tutorial2Test.cs:29) 2020-11-22 19:05:52.116 [04] | Enter DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:27) 2020-11-22 19:05:52.116 [04] | | 2020-11-22 19:05:52.116 [04] | | type = 2020-11-22 19:05:52.116 [04] | | System.RuntimeType System.ValueTuple`2[System.Int32,System.Int64] (Tutorial2.cs:28) 2020-11-22 19:05:52.118 [04] | | 2020-11-22 19:05:52.118 [04] | | typeArguments = System.Type[2] { 2020-11-22 19:05:52.118 [04] | | System.RuntimeType System.Int32, System.RuntimeType System.Int64 2020-11-22 19:05:52.118 [04] | | } (Tutorial2.cs:32) 2020-11-22 19:05:52.119 [04] | | 2020-11-22 19:05:52.119 [04] | | typeof(ValueTuple) = System.RuntimeType System.ValueTuple (Tutorial2.cs:35) 2020-11-22 19:05:52.119 [04] | | isValueTuple = false (Tutorial2.cs:36) 2020-11-22 19:05:52.120 [04] | | backets = ("<", ">") (Tutorial2.cs:40) 2020-11-22 19:05:52.120 [04] | | Enter DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:27) 2020-11-22 19:05:52.120 [04] | | | type = System.RuntimeType System.Int32 (Tutorial2.cs:28) 2020-11-22 19:05:52.121 [04] | | Leave DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:26) duration: 00:00:00.0003259 2020-11-22 19:05:52.121 [04] | | 2020-11-22 19:05:52.121 [04] | | Enter DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:27) 2020-11-22 19:05:52.124 [04] | | | type = System.RuntimeType System.Int64 (Tutorial2.cs:28) 2020-11-22 19:05:52.124 [04] | | Leave DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:26) duration: 00:00:00.0028577 2020-11-22 19:05:52.124 [04] | Leave DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:26) duration: 00:00:00.0085159
テストの最後のLeaveの出力がないのは、テストが失敗した場合は、途中で例外がスローされてテストメソッドのTrace.Leave()
が呼ばれないためです。そこでテストメソッドもtry {}
とfinally{}
を使用します。
using DebugTraceTutorial; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Data; using static DebugTrace.CSharp; // TODO: Delete after debugging namespace DebugtraceTutorial { [TestClass] public class Tutorial2Test { /** * GetNameのテスト。 * @param type 対象の型 * @param expected 予期する型名 */ [DataTestMethod] /* 1 */ [DataRow(typeof(string), "string")] /* 2 */ [DataRow(typeof(string[]), "string[]")] /* 3 */ [DataRow(typeof(string[][]), "string[][]")] /* 4 */ [DataRow(typeof(List<string>), "System.Collections.Generic.List<string>")] /* 5 */ [DataRow(typeof(List<string>[]), "System.Collections.Generic.List<string>[]")] /* 6 */ [DataRow(typeof((int, long)), "(int, long)")] /* 7 */ [DataRow(typeof(((byte, short), (int, long))), "((byte, short), (int, long))")] public void TestGetName(Type type, string expected) { try {Trace.Enter(); // TODO: Delete after debugging Trace.Print("type", type); // TODO: Delete after debugging Trace.Print("expected", expected); // TODO: Delete after debugging // when var typeName = Tutorial2.GetName(type); // then Assert.AreEqual(expected, typeName); } finally {Trace.Leave();} // TODO: Delete after debugging } } }
実行してみます。
2020-11-22 19:14:25.609 [04] Enter DebugtraceTutorial.Tutorial2Test.TestGetName(Type type, String expected) (Tutorial2Test.cs:27) 2020-11-22 19:14:25.609 [04] | 2020-11-22 19:14:25.609 [04] | type = 2020-11-22 19:14:25.609 [04] | System.RuntimeType System.ValueTuple`2[System.Int32,System.Int64] (Tutorial2Test.cs:28) 2020-11-22 19:14:25.609 [04] | 2020-11-22 19:14:25.609 [04] | expected = (Length:11)"(int, long)" (Tutorial2Test.cs:29) 2020-11-22 19:14:25.610 [04] | Enter DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:27) 2020-11-22 19:14:25.610 [04] | | 2020-11-22 19:14:25.610 [04] | | type = 2020-11-22 19:14:25.610 [04] | | System.RuntimeType System.ValueTuple`2[System.Int32,System.Int64] (Tutorial2.cs:28) 2020-11-22 19:14:25.612 [04] | | 2020-11-22 19:14:25.612 [04] | | typeArguments = System.Type[2] { 2020-11-22 19:14:25.612 [04] | | System.RuntimeType System.Int32, System.RuntimeType System.Int64 2020-11-22 19:14:25.612 [04] | | } (Tutorial2.cs:32) 2020-11-22 19:14:25.613 [04] | | 2020-11-22 19:14:25.613 [04] | | typeof(ValueTuple) = System.RuntimeType System.ValueTuple (Tutorial2.cs:35) 2020-11-22 19:14:25.614 [04] | | isValueTuple = false (Tutorial2.cs:36) 2020-11-22 19:14:25.614 [04] | | backets = ("<", ">") (Tutorial2.cs:40) 2020-11-22 19:14:25.614 [04] | | Enter DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:27) 2020-11-22 19:14:25.615 [04] | | | type = System.RuntimeType System.Int32 (Tutorial2.cs:28) 2020-11-22 19:14:25.615 [04] | | Leave DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:26) duration: 00:00:00.0003853 2020-11-22 19:14:25.615 [04] | | 2020-11-22 19:14:25.615 [04] | | Enter DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:27) 2020-11-22 19:14:25.619 [04] | | | type = System.RuntimeType System.Int64 (Tutorial2.cs:28) 2020-11-22 19:14:25.619 [04] | | Leave DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:26) duration: 00:00:00.0031286 2020-11-22 19:14:25.619 [04] | Leave DebugTraceTutorial.Tutorial2.GetName(Type type) (Tutorial2.cs:26) duration: 00:00:00.0090726 2020-11-22 19:14:25.622 [04] Leave DebugtraceTutorial.Tutorial2Test.TestGetName(Type type, String expected) (Tutorial2Test.cs:34) duration: 00:00:00.0128997
テストが失敗する原因は、type
がValueTuple
の場合は、true
になって欲しいvar isValueTuple = type == typeof(ValueTuple);
が原因なので、var isValueTuple = type.FullName.StartsWith("System.ValueTuple");
に修正します。
デバッグ終了後は、チュートリアル1と同様にデバッグ用コードを削除します。
まとめ
- チュートリアル1: デバッグコードを挿入する対象のメソッドの終了箇所が1箇所でかつ例外をスローしない場合
(または例外がスローされた場合にインデントが正しくなくても良い場合) - チュートリアル2: デバッグコードを挿入する対象のメソッドの終了箇所が複数か途中で例外をスローする可能性がある場合
以上でチュートリアルは終了です。
DebugTrace-java チュートリアル
DebugTraceについて
デバッグには主に2つの方法があります。
- EclipseやVisual StudioなどのIDEを使用してブレークポイントで停止させステップ実行する。
- プログラムの各所にデバッグ用のログ出力文を挿入する。
1の方法でバグが見つかる場合はそれで良いのですが、複数スレッドで動作するなど複雑なプログラムの場合は、(準備の手間はかかりますが)2の方法の方がバグを見つけやすい場合もあります。
DebugTraceは、2の方法でのデバッグを支援するライブラリです。言語毎にリリースされていて、以下があります。
DebugTrace-java
(Java 8以降)debugtrace-js
(JavaScript / Node.js 10以降)DebugTrace-net
(.NET Core 3.1以降 (C#, VisualBasic))DebugTrace-py
(Python 3.7以降)DebugTrace-cpp
(C++14以降)
リフレクションの機能の有無(C++にはない)、メモリ管理方法(ガベッジコレクションを行うかどうか)および命名規約によってそれぞれの言語用のDebugTraceのAPIは異なっていますが、できる事は主に以下の2つです。
- メソッド(あるいは関数)の開始時と終了時にログを出力する。またその間のログ出力のインデントを1増やす。
- 必要に応じてリフレクションを使用して変数内容をログに出力する。また見易くするため、必要に応じて改行、インデントを行う。
プロジェクト(Java)の作成
AdoptOpenJDKのOpenJDK 11 (LTS)およびEclipse IDE 2020-09 (4.17)を使用するのでインストールします。 JDKはAdoptOpenJDK以外でもかまいません。 Pleiadesを使用して日本語化している場合は、以下の内容を適宜日本語に置き換えてください。
- Eclipse起動後に
[File]-[New]-[Project...]
を実行します。 - Wizardの選択ダイアログが表示されるので、
Gradle/Gradle Project
を選択して[Next>]
をクリックします。 - Welcomeダイアログが表示されるので、
[Next>]
をクリックします。 - New Gradle Projectダイアログが表示されるので、
Project name:
にDebugTraceJavaTutorial
を入力します。 - 入力後
[Finish]
をクリックするとプロジェクトが作成されます。
生成されたbuild.gradle
を以下の内容に置き換えます。
plugins { id 'java-library' } repositories { jcenter() } dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.7.+' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.+' compileOnly 'org.debugtrace:debugtrace:3.+' testImplementation 'org.debugtrace:debugtrace:3.+' } sourceCompatibility = '11' targetCompatibility = '11'
生成されたソース(src/main/java内)をフォルダ毎削除して以下のJavaソースを追加します。
パス: src/main/java/org/debugtrace/tutorial/Tutorial1.java
package org.debugtrace.tutorial; public class Tutorial1 { public static int maxLogByteArrayLength = 1024; /** * バイト配列の文字列表現をStringBuilderに追加する。 * @param builder StringBuilder * @param bytes バイト配列 * @return 引数のbuilder */ public static StringBuilder appendBytes(StringBuilder builder, byte[] bytes) { builder.append('['); var delimiter = ""; for (var index = 0; index < bytes.length; ++index) { builder.append(delimiter); if (index >= maxLogByteArrayLength) { builder.append("..."); break; } var value = bytes[index]; if (value < 0) value += 256; var ch = (char)(value / 16 + '0'); if (ch > '9') ch += 'A' - '9' - 1; builder.append(ch); ch = (char)(value % 16 + '0'); if (ch > '9') ch += 'A' - '9' - 1; builder.append(ch); delimiter = ", "; } builder.append(']'); return builder; } }
生成されたテストソース(src/test/java内)をフォルダ毎削除して以下のJavaソースを追加します。
パス: src/test/java/org/debugtrace/tutorial/Tutorial1Test.java
package org.debugtrace.tutorial; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; public class Tutorial1Test { /** * appendBytesのテスト。 * @param bytes バイト配列 * @param expected StringBuilderに追加されると期待する文字列 */ @ParameterizedTest @MethodSource("testAppendBytesParams") public void testAppendBytes(byte[] bytes, String expected) { // when var builder = Tutorial1.appendBytes(new StringBuilder(), bytes); // then assertEquals(expected, builder.toString()); } static Stream<Arguments> testAppendBytesParams() { return Stream.of( Arguments.of(new byte[] {}, "[]"), Arguments.of(new byte[] {1}, "[01]"), Arguments.of( new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F]" ), Arguments.of( new byte[] {-16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1}, "[F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, FA, FB, FC, FD, FE, FF]" ) ); } }
チュートリアル1
上記のtestAppendBytes
はパラメータ付きのテストメソッドで、testAppendBytesParams
を呼び出して引数を受け取ります。
Arguments.of(...)
が一度に渡す引数のセットになっています。
テストを実行すると最初の3つは成功しますが、最後の1つで失敗します。
org.opentest4j.AssertionFailedError: expected: <[F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, FA, FB, FC, FD, FE, FF]> but was: <[/0, 0!, 0", 0#, 0$, 0%, 0&, 0', 0(, 0), 0*, 0+, 0,, 0-, 0., 0/]>
対象のメソッドとテストメソッドにDebugTrace-javaのメソッドを呼び出すコードを挿入してみます。
package org.debugtrace.tutorial; import org.debugtrace.DebugTrace; public class Tutorial1 { public static int maxLogByteArrayLength = 1024; /** * バイト配列の文字列表現をStringBuilderに追加する。 * @param builder StringBuilder * @param bytes バイト配列 * @return 引数のbuilder */ public static StringBuilder appendBytes(StringBuilder builder, byte[] bytes) { DebugTrace.enter(); // TODO: Delete after debugging DebugTrace.print("bytes", bytes); // TODO: Delete after debugging builder.append('['); var delimiter = ""; for (var index = 0; index < bytes.length; ++index) { DebugTrace.print("index", index); // TODO: Delete after debugging builder.append(delimiter); if (index >= maxLogByteArrayLength) { builder.append("..."); break; } var value = bytes[index]; DebugTrace.print("1 value", value); // TODO: Delete after debugging if (value < 0) value += 256; DebugTrace.print("2 value", value); // TODO: Delete after debugging var ch = (char)(value / 16 + '0'); DebugTrace.print("1-1 ch", ch); // TODO: Delete after debugging if (ch > '9') ch += 'A' - '9' - 1; DebugTrace.print("1-2 ch", ch); // TODO: Delete after debugging builder.append(ch); ch = (char)(value % 16 + '0'); DebugTrace.print("2-1 ch", ch); // TODO: Delete after debugging if (ch > '9') ch += 'A' - '9' - 1; DebugTrace.print("2-2 ch", ch); // TODO: Delete after debugging builder.append(ch); delimiter = ", "; } builder.append(']'); DebugTrace.leave(); // TODO: Delete after debugging return builder; } }
package org.debugtrace.tutorial; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.stream.Stream; import org.debugtrace.DebugTrace; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; public class Tutorial1Test { /** * appendBytesのテスト。 * @param bytes バイト配列 * @param expected StringBuilderに追加されると期待する文字列 */ @ParameterizedTest @MethodSource("testAppendBytesParams") public void testAppendBytes(byte[] bytes, String expected) { DebugTrace.enter(); // TODO: Delete after debugging // when var builder = Tutorial1.appendBytes(new StringBuilder(), bytes); // then assertEquals(expected, builder.toString()); DebugTrace.leave(); // TODO: Delete after debugging } static Stream<Arguments> testAppendBytesParams() { return Stream.of( Arguments.of(new byte[] {}, "[]"), Arguments.of(new byte[] {1}, "[01]"), Arguments.of( new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F]" ), Arguments.of( new byte[] {-16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1}, "[F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, FA, FB, FC, FD, FE, FF]" ) ); } }
もう一度テストを実行してみます。以下は上記を実行したログの一部です。ログはデフォルトで標準エラー(stderr)に出力されます。Eclipseで実行した場合は、Consoleに出力されます。
2020-11-02 14:59:58.346+09:00 Enter org.debugtrace.tutorial.Tutorial1Test.testAppendBytes (Tutorial1Test.java:20) 2020-11-02 14:59:58.347+09:00 | Enter org.debugtrace.tutorial.Tutorial1.appendBytes (Tutorial1.java:16) 2020-11-02 14:59:58.347+09:00 | | bytes = (byte[16])[F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF] (Tutorial1.java:17) 2020-11-02 14:59:58.347+09:00 | | index = 0 (Tutorial1.java:21) 2020-11-02 14:59:58.348+09:00 | | 1 value = (byte)-16 (Tutorial1.java:28) 2020-11-02 14:59:58.348+09:00 | | 2 value = (byte)-16 (Tutorial1.java:30) 2020-11-02 14:59:58.348+09:00 | | 1-1 ch = '/' (Tutorial1.java:32) 2020-11-02 14:59:58.348+09:00 | | 1-2 ch = '/' (Tutorial1.java:34) 2020-11-02 14:59:58.349+09:00 | | 2-1 ch = '0' (Tutorial1.java:37) 2020-11-02 14:59:58.349+09:00 | | 2-2 ch = '0' (Tutorial1.java:39)
バイト配列の最初のF0
の1桁目のF
が'/'
に間違って変換されています。2 value =
の箇所は正値を期待しているのですが、負値のままです。
これはvar value = bytes[index];
で、value
変数がbyte
なのが原因なので、var value = (int)bytes[index];
に修正します。
修正後もう一度テストを実行すると成功します。ログの一部は以下になります。
2020-11-02 15:03:51.930+09:00 Enter org.debugtrace.tutorial.Tutorial1Test.testAppendBytes (Tutorial1Test.java:20) 2020-11-02 15:03:51.931+09:00 | Enter org.debugtrace.tutorial.Tutorial1.appendBytes (Tutorial1.java:16) 2020-11-02 15:03:51.931+09:00 | | bytes = (byte[16])[F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF] (Tutorial1.java:17) 2020-11-02 15:03:51.931+09:00 | | index = 0 (Tutorial1.java:21) 2020-11-02 15:03:51.932+09:00 | | 1 value = -16 (Tutorial1.java:29) 2020-11-02 15:03:51.932+09:00 | | 2 value = 240 (Tutorial1.java:31) 2020-11-02 15:03:51.932+09:00 | | 1-1 ch = '?' (Tutorial1.java:33) 2020-11-02 15:03:51.932+09:00 | | 1-2 ch = 'F' (Tutorial1.java:35) 2020-11-02 15:03:51.933+09:00 | | 2-1 ch = '0' (Tutorial1.java:38) 2020-11-02 15:03:51.933+09:00 | | 2-2 ch = '0' (Tutorial1.java:40)
デバッグの終了後にデバッグ用コードを削除するには、正規表現で検索して空文字列に置換します。改行コードも削除しているのでデバッグ用コードを挿入する前のソースに戻ります。
正規表現検索文字列: ^.+DebugTrace.*\r?\n
チュートリアル2
次のチュートリアルのソースとテストソースは以下です。
パス: src/main/java/org/debugtrace/tutorial/Tutorial2.java
package org.debugtrace.tutorial; import java.util.Objects; public class Tutorial2 { /** * クラス名を返します。ただしjava.xxxパッケージの場合はパッケージ名を取り除いて返します。 * @param clazz クラス * @return クラス名 * @throws NullPointerException clazzがnullの場合 */ public static String getName(Class<?> clazz) { if (Objects.requireNonNull(clazz, "clazz is null.").isArray()) return getName(clazz.getComponentType()) + "[]"; String className = clazz.getName(); if (className.startsWith("java")) return className.substring(className.lastIndexOf('.') + 1); return className; } }
パス: src/test/java/org/debugtrace/tutorial/Tutorial2Test.java
package org.debugtrace.tutorial; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.stream.Stream; import javax.sql.RowSet; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; public class Tutorial2Test { /** * getNameのテスト。 * @param clazz クラス * @param expected 期待するクラス名 */ @ParameterizedTest @MethodSource("testGetNameParams") public void testGetName(Class<?> clazz, String expected) { // when var className = Tutorial2.getName(clazz); // then assertEquals(expected, className); } static Stream<Arguments> testGetNameParams() { return Stream.of( Arguments.of(String.class, "String"), Arguments.of(String[].class, "String[]"), Arguments.of(String[][].class, "String[][]"), Arguments.of(RowSet.class, "javax.sql.RowSet"), Arguments.of(RowSet[].class, "javax.sql.RowSet[]"), Arguments.of(RowSet[][].class, "javax.sql.RowSet[][]") ); } }
実行すると最初の3つで成功しますが、後の3つでは失敗します。
org.opentest4j.AssertionFailedError: expected: <javax.sql.RowSet> but was: <RowSet>
デバッグコードを挿入しますが、メソッドが終了する箇所が3箇所あるため、DebugTrace.leave
メソッドの呼び出しをgetName
メソッドの最後に挿入だけでは不十分です。
この場合は、以下のようにします。
package org.debugtrace.tutorial; import java.util.Objects; import org.debugtrace.DebugTrace; public class Tutorial2 { /** * クラス名を返します。ただしjava.xxxパッケージの場合はパッケージ名を取り除いて返します。 * @param clazz クラス * @return クラス名 * @throws NullPointerException clazzがnullの場合 */ public static String getName(Class<?> clazz) { try {DebugTrace.enter(); // TODO: Delete after debugging DebugTrace.print("clazz", clazz); // TODO: Delete after debugging if (Objects.requireNonNull(clazz, "clazz is null.").isArray()) return getName(clazz.getComponentType()) + "[]"; String className = clazz.getName(); DebugTrace.print("className", className); // TODO: Delete after debugging DebugTrace.print("className.startsWith(\"java\")", className.startsWith("java")); // TODO: Delete after debugging if (className.startsWith("java")) return className.substring(className.lastIndexOf('.') + 1); return className; } finally {DebugTrace.leave();} // TODO: Delete after debugging } }
テスト対象のメソッド全体をtry {DebugTrace.enter();
と} finally {DebugTrace.leave();}
で囲います。
テストメソッドの方はチュートリアル1と同様にします。
テストを実行すると以下のログ(失敗している部分)が出力されます。
2020-11-02 15:28:39.405+09:00 Enter org.debugtrace.tutorial.Tutorial2Test.testGetName (Tutorial2Test.java:22) 2020-11-02 15:28:39.408+09:00 | Enter org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:16) 2020-11-02 15:28:39.414+09:00 | | clazz = (Class)interface javax.sql.RowSet (Tutorial2.java:17) 2020-11-02 15:28:39.415+09:00 | | className = (length:16)"javax.sql.RowSet" (Tutorial2.java:22) 2020-11-02 15:28:39.418+09:00 | | className.startsWith("java") = true (Tutorial2.java:23) 2020-11-02 15:28:39.420+09:00 | Leave org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:28) duration: 00:00:00.005 2020-11-02 15:28:39.444+09:00 | 2020-11-02 15:28:39.450+09:00 | Enter org.debugtrace.tutorial.Tutorial2Test.testGetName (Tutorial2Test.java:22) 2020-11-02 15:28:39.450+09:00 | | Enter org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:16) 2020-11-02 15:28:39.451+09:00 | | | clazz = (Class)class [Ljavax.sql.RowSet; (Tutorial2.java:17) 2020-11-02 15:28:39.451+09:00 | | | Enter org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:16) 2020-11-02 15:28:39.451+09:00 | | | | clazz = (Class)interface javax.sql.RowSet (Tutorial2.java:17) 2020-11-02 15:28:39.451+09:00 | | | | className = (length:16)"javax.sql.RowSet" (Tutorial2.java:22) 2020-11-02 15:28:39.458+09:00 | | | | className.startsWith("java") = true (Tutorial2.java:23) 2020-11-02 15:28:39.461+09:00 | | | Leave org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:28) duration: 00:00:00.006 2020-11-02 15:28:39.461+09:00 | | Leave org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:28) duration: 00:00:00.010 2020-11-02 15:28:39.474+09:00 | | 2020-11-02 15:28:39.475+09:00 | | Enter org.debugtrace.tutorial.Tutorial2Test.testGetName (Tutorial2Test.java:22) 2020-11-02 15:28:39.476+09:00 | | | Enter org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:16) 2020-11-02 15:28:39.476+09:00 | | | | clazz = (Class)class [[Ljavax.sql.RowSet; (Tutorial2.java:17) 2020-11-02 15:28:39.476+09:00 | | | | Enter org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:16) 2020-11-02 15:28:39.477+09:00 | | | | | clazz = (Class)class [Ljavax.sql.RowSet; (Tutorial2.java:17) 2020-11-02 15:28:39.483+09:00 | | | | | Enter org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:16) 2020-11-02 15:28:39.484+09:00 | | | | | | clazz = (Class)interface javax.sql.RowSet (Tutorial2.java:17) 2020-11-02 15:28:39.485+09:00 | | | | | | className = (length:16)"javax.sql.RowSet" (Tutorial2.java:22) 2020-11-02 15:28:39.491+09:00 | | | | | | className.startsWith("java") = true (Tutorial2.java:23) 2020-11-02 15:28:39.491+09:00 | | | | | Leave org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:28) duration: 00:00:00.007 2020-11-02 15:28:39.492+09:00 | | | | Leave org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:28) duration: 00:00:00.014 2020-11-02 15:28:39.492+09:00 | | | Leave org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:28) duration: 00:00:00.016
テスト毎にネストが深くなっていきますが、テストが失敗した場合は、途中で例外がスローされてテストメソッドのDebugTrace.leave()
が呼ばれないためです。そこでテストメソッドもtry {}
とfinally{}
を使用します。
package org.debugtrace.tutorial; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.stream.Stream; import javax.sql.RowSet; import org.debugtrace.DebugTrace; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; public class Tutorial2Test { /** * getNameのテスト。 * @param clazz クラス * @param expected 期待するクラス名 */ @ParameterizedTest @MethodSource("testGetNameParams") public void testGetName(Class<?> clazz, String expected) { try {DebugTrace.enter(); // TODO: Delete after debugging // when var className = Tutorial2.getName(clazz); // then assertEquals(expected, className); } finally {DebugTrace.leave();} // TODO: Delete after debugging } static Stream<Arguments> testGetNameParams() { return Stream.of( Arguments.of(String.class, "String"), Arguments.of(String[].class, "String[]"), Arguments.of(String[][].class, "String[][]"), Arguments.of(RowSet.class, "javax.sql.RowSet"), Arguments.of(RowSet[].class, "javax.sql.RowSet[]"), Arguments.of(RowSet[][].class, "javax.sql.RowSet[][]") ); } }
実行してみます。
2020-11-02 15:51:39.386+09:00 Enter org.debugtrace.tutorial.Tutorial2Test.testGetName (Tutorial2Test.java:21) 2020-11-02 15:51:39.387+09:00 | Enter org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:15) 2020-11-02 15:51:39.387+09:00 | | clazz = (Class)interface javax.sql.RowSet (Tutorial2.java:16) 2020-11-02 15:51:39.388+09:00 | | className = (length:16)"javax.sql.RowSet" (Tutorial2.java:21) 2020-11-02 15:51:39.388+09:00 | | className.startsWith("java") = true (Tutorial2.java:22) 2020-11-02 15:51:39.388+09:00 | Leave org.debugtrace.tutorial.Tutorial2.getName (Tutorial2.java:27) duration: 00:00:00.001 2020-11-02 15:51:39.390+09:00 Leave org.debugtrace.tutorial.Tutorial2Test.testGetName (Tutorial2Test.java:27) duration: 00:00:00.002
テストが失敗する原因は、javax.sql.RowSet
の場合は、false
になって欲しいif (className.startsWith("java"))
が原因なので、if (className.startsWith("java."))
に修正します。
デバッグ終了後は、チュートリアル1と同様にデバッグ用コードを削除します。
まとめ
- チュートリアル1: デバッグコードを挿入する対象のメソッドの終了箇所が1箇所でかつ例外をスローしない場合
(または例外がスローされた場合にインデントが正しくなくても良い場合) - チュートリアル2: デバッグコードを挿入する対象のメソッドの終了箇所が複数か途中で例外をスローする可能性がある場合
以上でチュートリアルは終了です。
各種DebugTraceを(アップデート)リリースしました
- DebugTrace-java (Java 8 ~) - README_ja GitHub
- 3.0.2 - 2020/7/6 - リリース GitHub
- 3.0.1 - 2020/5/15
- 3.0.0 - 2020/5/12
- DebugTrace-net (.NET Core 3.1 ~) - README_ja GitHub
- 2.0.2 - 2020/7/12 - リリース GitHub
- 2.0.1 - 2020/5/16
- 2.0.0 - 2020/5/12
- debugtrace-js (Node.js 10 ~) - README_ja GitHub
- 2.0.0 - 2020/8/2 - リリース GitHub
- DebugTrace-py (Python 3.7 ~) - README_ja GitHub
- 1.0.1 - 2020/7/19 - リリース GitHub
- 1.0.0 - 2020/5/26
Lightsleep 3.0.0をリリースしました
DateTime APIのデータ型に対する変換機能を追加しました。
エンティティクラスのフィールド型として以下の型をDate
, Time
, Timestamp
の代わりに使用できます。
java.time.LocalDate
java.time.LocalTime
java.time.LocalDateTime
java.time.OffsetDateTime
java.time.ZonedDateTime
java.time.Instant
リリース詳細: GitHub releases