Update copilot references

This commit is contained in:
vczh
2026-02-03 16:13:37 -08:00
parent 8d0dcb17e3
commit e4684b17b2
31 changed files with 661 additions and 132 deletions

View File

@@ -830,3 +830,16 @@ It provides a comprehensive testing framework, XML-to-C++ compilation, and integ
- [TAB and Control Focus](./manual/gacui/advanced/tab.md)
- [Creating New List Controls](./manual/gacui/advanced/impllistcontrol.md)
- [Porting GacUI to Other Platforms](./manual/gacui/advanced/implosprovider.md)
- [Hosted Mode and Remote Protocol](./manual/gacui/modes/home.md)
- [Remote Protocol Core Application](./manual/gacui/modes/remote_core.md)
- [Remote Protocol Client Application](./manual/gacui/modes/remote_client.md)
- [Implementing a Communication Protocol](./manual/gacui/modes/remote_communication.md)
## Unit Testing
- [Using Unit Test Framework](./manual/unittest/vlpp.md)
- [Running GacUI in Unit Test Framework](./manual/unittest/gacui.md)
- [Snapshots and Frames](./manual/unittest/gacui_frame.md)
- [IO Interaction](./manual/unittest/gacui_io.md)
- [Accessing Controls](./manual/unittest/gacui_controls.md)
- [Snapshot Viewer](./manual/unittest/gacui_snapshots.md)

View File

@@ -1,6 +1,6 @@
# Application
A GacUI application requires a user defined function **void GuiMain(void)**. In order to start a GacUI application, one of the following functions must be called in entry function. - For Windows, One of the following functions must be called in **WinMain** to start the application: - SetupWindowsDirect2DRenderer - SetupHostedWindowsDirect2DRenderer - SetupWindowsGDIRenderer - SetupHostedWindowsGDIRenderer They initialize necessary objects and call **GuiMain** with different rendering techniques. - For macOS, **SetupOSXCoreGraphicsRenderer** must be called in **main**. - For Linux, **SetupGGacRenderer** must be called in **main**.
A GacUI application requires a user defined function **void GuiMain(void)**. In order to start a GacUI application, one of the following functions must be called in entry function. - For Windows, One of the following functions must be called in **WinMain** to start the application: - SetupWindowsDirect2DRenderer - SetupHostedWindowsDirect2DRenderer - SetupWindowsGDIRenderer - SetupHostedWindowsGDIRenderer They initialize necessary objects and call **GuiMain** with different rendering techniques. - For macOS, **SetupOSXCoreGraphicsRenderer** must be called in **main**. - For Linux Wayland, **SetupWGacRenderer** must be called in **main**. - For Linux XWindow, **SetupGGacRenderer** must be called in **main**.
When **GuiMain** is called, the **GuiApplication** object is ready, which can be accessed by the **GetApplication** function. A typical **GuiMain** function looks like: ``` void GuiMain() { // 1. create a window, anything that inherits from GuiWindow works MyWindow myWindow; // 2. decide where to show the window, it could be any location in any monitor you like myWindow.MoveToScreenCenter() // 3. show the window as a main window of the application GetApplication()->Run(&myWindow); // 4. when the main window is closed, it gets here } ```
@@ -20,3 +20,11 @@ For example, when you put a button in a tab header to remove the tab page, you c
**RunGuiTask** works like **InvokeInMainThreadAndWait**, but it is OK to call in the UI thread. It will return only after the task is completed.
**SetupWindowsGDIRendererInternal** and **SetupHostedWindowsDirect2DRenderer** setup GacUI with only one OS native window. All non-main windows will be renderered inside that window.
**SetupRemoteNativeController** setup GacUI with remote protocol, it is also running in hosted mode. It takes an argument to an implementation of **IGuiRemoteProtocol**, which is responsible to serialize and deserialize commands between core and renderer.
**SetupRawWindowsGDIRenderer** and **SetupRawWindowsDirect2DRenderer** starts up a GacUI application with **INativeController** only, there will be no **GuiApplication** running and therefore no controls can be used. This is useful when you want to run a remote protocol renderer that interacts with a user. **GuiRemoteRendererSingle** connects to one actual GacUI application that running with remote protoco. **IMPORTANT:** you must implement the communicating by yourself. GacUI has a default implementation of remote protocol serialization with JSON or string format. But you must implement the sending and receiving.
When running GacUI with unit test framework, it starts GacUI with remote protocol, with an implementation of **IGuiRemoteProtocol** that logs the whole UI to multiple JSON files. **UnitTestSnapshotViewerAppWindow** and **UnitTestSnapshotViewerViewModel** offers an ability to render these JSON files with limited user interaction to inspect what happened during the unit tests. In order to ensure a test case creates exactly the same logging across multiple platforms, text measurments are fake and limited to simple layout. Therefore in the snapshot viewer you might find controls for displaying texts (e.g. a button) appear to be larger than expected, that is normal.

View File

@@ -36,9 +36,13 @@ For graphics API like DirectX, sometimes the device context will be invalid and
**INativeController** consists of multiple services: - **INativeCallbackService**: Managing **INativeControllerListener** objects so that different components can receive system messages and keep the code decoupled. - **INativeAsyncService**: Managing and executing task queues in multiple threads. - **INativeResourceService**: Accessing cursors and fonts from the OS. - **INativeClipboardService**: Accessing clipboard from the OS. - **INativeScreenService**: Accessing physical monitors from the OS. - **INativeDialogService**: Accessing default dialogs from the OS. - **INativeImageService**: Loading image from files or memories. - **INativeWindowService**: Managing windows using OS's window manager. - **INativeInputService**: Accessing global keyboard status and timer.
## Hosted Mode
**GuiHostedController** and **SetHostedApplication** can create a hosted mode implementation of **INativeController** on top of a native **INativeController** instance. Checkout **SetupWindowsGDIRendererInternal** and **SetupHostedWindowsDirect2DRenderer** for details. When running GacUI with remote protocol, **SetupRemoteNativeController** will create **GuiHostedController** on top of **GuiRemoteController**.
When an **INativeController** is running as a native OS provider to **GuiHostedController**, it has to handle extra works for rendering in **IGuiGraphicsRenderTarget**: - **IsInHostedRendering()** will be called to detect if the render target is working. - **StartHostedRendering()**. This is called only under hosted mode, so that all subsequent rendering calls are hinted. - Multiple **StartRendering()** and **StopRendering()** calls sent from the window. - **StopHostedRendering()**, if any **StopRendering()** fails, this should fail with the same reason.**GuiGraphicsRenderTarget** can be used to simplifies the implementation, therefore only one call to **StartRenderingOnNativeWindow** and **StopRenderingOnNativeWindow** is sent to the render target.
## Development Note
Implementing **INativeController** is a super complex topic. In order to port GacUI to a new platform, you must know everything about how GacUI interact with the OS. It is highly recommended to read the [Windows](https://github.com/vczh-libraries/GacUI/tree/master/Source/NativeWindow/Windows), [macOS](https://github.com/vczh-libraries/iGac) and [Linux](https://github.com/vczh-libraries/gGac) implementation for **INativeController** before creating yours. Especially there are GDI and Direct2D support in Windows, it is a good example to know how to allow multiple rendering techniques in one OS.
In the future, **INativeHostedController** and **INativeHostedWindow** will be created for scenarios that all GacUI windows are rendered in one OS native window, like CLI, UWP or browsers.
Implementing **INativeController** is a super complex topic. In order to port GacUI to a new platform, you must know everything about how GacUI interact with the OS. It is highly recommended to read the [Windows](https://github.com/vczh-libraries/GacUI/tree/master/Source/NativeWindow/Windows), [macOS](https://github.com/vczh-libraries/iGac), [Linux Wayland](https://github.com/vczh-libraries/wGac) and [Linux XWindow](https://github.com/vczh-libraries/gGac) implementation for **INativeController** before creating yours. Especially there are GDI and Direct2D support in Windows, it is a good example to know how to allow multiple rendering techniques in one OS.

View File

@@ -0,0 +1,14 @@
# Hosted Mode and Remote Protocol
Using **SetupHostedWindowsGDIRenderer** or **SetupHostedWindowsDirect2DRenderer** instead of **SetupWindowsGDIRenderer** or **SetupWindowsDirect2DRenderer** runs a GacUI application in hosted mode. A hosted mode GacUI application will start only one OS native window, other windows are rendered inside it virtually. System dialogs will be replaced by predefined GacUI implemented dialogs by default, so that anything will be strictly inside the OS native window.
Using **SetupRemoteNativeController** runs a GacUI application with remote protocol (forced in hosted mode), which becomes headless, instead of render anything on the screen, it sends out rendering commands to a remote client. This part will be covered in [Remote Protocol Core Application](../.././gacui/modes/remote_core.md).
If you want a remote protocol renderer that still work with other components of GacUI, **GuiRemoteRendererSingle** implements everything that the remote protocol needs and starts a window, displaying anything from the core GacUI application, with user interactions covered. This part will be covered in [Remote Protocol Client Application](../.././gacui/modes/remote_client.md).
The [GacJS](https://github.com/vczh-libraries/GacJS) also implements a GacUI renderer in a browser, connecting to a GacUI application via HTTP protocol. But the HTTP protocol implementation is not official, it is for demo only, no security guarantee is provided.
GacUI does not provide any protocol implementation, you need to connect the GacUI application with a remote client by yourself. And you are free to go with and not limited to anything below: - Network protocols such as TCP, HTTP, HTTPS, NamedPipe, etc. For HTTP and NamedPipd, the **GacUI** repo has a test client as a demo. - Non-networking protocols such as Foreign Function Interface (FFI) or even Dynamic-Linked Library (DLL). But GacUI still offers some wrappers that simplifies everything to string sending and receiving. This part will be covered in [Implementing a Communication Protocol](../.././gacui/modes/remote_communication.md).
In [this folder](https://github.com/vczh-libraries/Release/tree/master/Import/Metadata) there are definitions of the remote protocol in JSON format and in its original format. The original format can be parsed using **vl::presentation::remoteprotocol::Parser** in **GacUICompiler.h**. You can then write any code generation tool to generate code in any programming language you want.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
# Implementing a Communication Protocol
**IGuiRemoteProtocol** has a lot of strong typed messages defined as functions. When the core application wants to send anything to a remote client, a member function is called. When the remote client sent anything to the core application, a member function in **IGuiRemoteProtocol::GetRemoteEventProcessor** is called. Some messages need to wait for a response from the remote client, the first argument of such message is always **vint id**. The core application will be blocked until the remote client sends back a expected response message with the same id.
Therefore **IGuiRemoteProtocol** represents a renderer. The core side implementation sends messages to client and receive responses from client. The client side implementation receives messages from core and send responses to core.
**IGuiRemoteProtocolChannel\<T\>** and **IGuiRemoteProtocolChannelReceiver\<T\>** works like **IGuiRemoteProtocol** and **IGuiRemoteEventProcessor**, but there is no strong typed messages anymore, everything becomes the **T**.
**GuiRemoteProtocolFromJsonChannel** (for core) and **GuiRemoteJsonChannelFromProtocol** (for client) handles message translation between **IGuiRemoteProtocol** and **IGuiRemoteProtocolChannel\<Ptr\<JsonObject\>\>**.
**GuiRemoteJsonChannelStringSerializer** (for core) and **GuiRemoteJsonChannelStringDeserializer** (for client) handles message translation between **IGuiRemoteProtocolChannel\<Ptr\<JsonObject\>\>** and **IGuiRemoteProtocolChannel\<WString\>**.
Although message are sent bidirectionally, but the naming is saying what is done from core to client. [Remote Protocol Client Application](../.././gacui/modes/remote_client.md) and [Implementing a Communication Protocol](../.././gacui/modes/remote_communication.md) demos how to setup the pipeline from **IGuiRemoteProtocol** all the way to sending and receiving strings. You can also implements your own pipeline using different message formats. JSON serialization is offered in GacUI by default, but you can also code generate your own serializer with other underlying format using metadata in [this folder](https://github.com/vczh-libraries/Release/tree/master/Import/Metadata).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,24 @@
# Accessing Controls
When a window or a custom control is created from GacUI XML Resource, and you need the pointer to a specific sub object, you don't have to **public** them. Here are functions to access a specific control, composition or component by its **ref.Name** attribute, or to access a control by its text, defined in the **vl::presentation::controls** namespace.
## FindObjectByName(rootObject, name)
The first parameter must be an object that created by an \<Instance/\>, e.g. a window or a custom control. The second parameter must be an object that directly inside that particular \<Instance/\> with **ref.Name** attribute assigned. If nothing is found, the function crashes. ```c++ auto button = FindObjectByName(window, L"buttonSomething"); ```
## TryFindObjectByName(rootObject, name)
Same as **FindObjectByName**, but it returns null if nothing is found.
## FindObjectByName(rootObject, name1, name2, ...)
This is a shortcut of a chain call: to **... FindObjectByName(FindObjectByName(rootObject, name1), name2) ...**. It is useful when your target object is inside nested custom controls in a window, you need to provide all **ref.Name** attributes in the hierarchy. **IMPORTANT:** such custom controls must be created by \<Instance/\>.
## FindControlByText\<Type\>(control, text)
The first parameter must be a control. The is the text of the target sub control. If nothing is found matching the type and the text, the function crashes. Otherwise it would return a random one within all matching controls. Unlike **FindObjectByName**, you can use this function to search into nested custom controls. ```c++ auto button = FindControlByText<GuiButton>(window, L"Click Me!"); ```
## TryFindControlByText\<Type\>(control, text)
Same as **FindControlByText**, but it returns null if nothing is found.

View File

@@ -0,0 +1,14 @@
# Snapshots and Frames
If you let the unit test framework create a window from a GacUI XML resource, You can set the 4th parametero of **GacUIUnitTest_StartFast_WithResourceAsText** to offer some callbacks. The type is **GacUIUnitTest_Installer** and it has these members: - **Func\<void()\> initialize;** it is called before the window is created. - **Func\<void(GuiWindow*)\> installWindow;** it is called before the window is opened. - **Func\<void()\> finalize;** it is called after the window is closed. - **Nullable\<UnitTestScreenConfig\> config;** more configurations, including font list, screen size, etc.
A test case typically begins with a call to **GacUIUnitTest_SetGuiMainProxy** with multiple frames created. You are recommended to follow the rule: - The name of the first frame should be **Ready**. - Names of other frames describe what has been done in the previous frame. - In each frame visible UI changes must be triggered. - As each IO operation is completed after all events are handled in the GacUI application, if an operation calls any blocking function, wrap it in **InvokeInMainThread**. - When an action is taken, if the side effect happens immediately, assertion should be placed right after the action. - When an action is taken, if the side effect happens async but fast, assertion should be placed at the beginning of the next frame. - Whenever possible keep the last frame only calling the **window-\>Close();** function. This rule could sometimes conflict with the previous rule, the previous rule has higher priority.
## About Visible UI Change
A visible UI change means something that cause GacUI to refresh any window in your application. This includes changing texts, sizes, layouts; showing or hiding windows or controls; enabling or disabling controls; select anything or type anything in a control; etc. The unit test framework will detect whether any visible UI change has been made in a frame, and the test fail if there is not. **IMPORTANT:** DO NOT make a frame just for assertion as it doesn't cause any visible UI change.
## About Blocking Function
Blocking functions are anything that does not return but the application is still running. For example, **ShowDialog**, **ShowModal**, **ShowModalAndDelete**, **InvokeInMainThreadAndWait**, etc. Any IO interaction inside a frame only returns after GacUI handles all events. For example, **protocol-\>LClick(location);** on a button only returns after **Clicked** as well as all other mouse events are handled. If in any of this event a blocking function is called, **LClick** blocks the current frame, and the unit test framework detects such action and fail the test. In this case, call **LClick** in **InvokeInMainThread** instead. ```c++ GetApplication()->InvokeInMainThread(window, [=]() { protocol->LClick(location); }); ```

View File

@@ -0,0 +1,16 @@
# IO Interaction
To calculate the location of a control or a composition for a following mouse interaction, use **protocol-\>LocationOf(controlOrComposition, ratioX?, ratioY?, offsetX?, offsetY?)**. It calculates the global location within an object using **left + width * ratioX + offsetX** and **top + height * ratioY + offsetY**. You can omit the last 4 parameters to get the center of the object. Such location can be passed to mouse interaction functions directly.
## Mouse Interactions
Here are a list of available mouse interaction functions in the **protocol** object: - **MouseMove(location)**: move the mouse to the specified location - **_LDown(location?)**: trigger a left button down event. - **_LUp(location?)**: trigger a left button up event. - **_LDBClick(location?)**: trigger a left button double click event. - **LClick(location?) and LClick(location, ctrl, shift, alt)**: trigger multiple events to simulate a click with optional modifier keys. - **LDBClick(location?) and LDBClick(location, ctrl, shift, alt)**: trigger multiple events to simulate a double click with optional modifier keys. - M (middle button) version and R (right button) version of above functions.
## Mouse Wheel Interactions
Here are a list of available mouse wheel interaction functions in the **protocol** object: - **_Wheel(up, position?) and _Wheel(up, position, ctrl, shift, alt)**: scroll the wheel forward. A negative number means backard. **WheelDown** or **WheelUp** for shortcuts to scroll with jumps, a jump scales to 120. - **_HWheel(right, position?) and _HWheel(right, position, ctrl, shift, alt)**: scroll the horizontal wheel towards right. A negative number means towards left. **HWheelLeft** or **HWheelRight** for shortcuts to scroll with jumps, a jump scales to 120.
## Key Interactions
Here are a list of available key interaction functions in the **protocol** object: - **_KeyDown(key)**: trigger a key down event. - **_KeyDownRepeat(key)**: trigger a key down event triggers by keep pressing a key. - **_KeyUp(key)**: trigger a key up event. - **KeyPress(key) and KeyPress(key, ctrl, shift, alt)**: trigger multiple events to simulate a key press with optional modifier keys. - **TypeString(string)**: trigger a sequence of char events to type a string. Key events will not be triggered.

View File

@@ -0,0 +1,4 @@
# Snapshot Viewer
After running the GacUI unit test, snapshots will be stored in a folder specified by **UnitTestFrameworkConfig::snapshotFolder**, which is passed to **GacUIUnitTest_Initialize** in the **main** function. You can write your own snapshot viewer to render any snapshot from a test run like this. ```c++ #include <GacUI.UnitTest.UI.h> #include <Windows.h> using namespace vl; using namespace vl::filesystem; using namespace vl::presentation; using namespace vl::presentation::controls; using namespace vl::presentation::unittest; using namespace gaclib_controls; void GuiMain() { theme::RegisterTheme(Ptr(new darkskin::Theme)); { // you can use GetApplication()->GetExecutablePath() if hardcoding a relative path to the folder contains the executable is possible. FilePath snapshotFolderPath = ...; UnitTestSnapshotViewerAppWindow window(Ptr(new UnitTestSnapshotViewerViewModel(snapshotFolderPath))); window.ForceCalculateSizeImmediately(); window.MoveToScreenCenter(); window.WindowOpened.AttachLambda([&](auto&&...) { window.ShowMaximized(); }); GetApplication()->Run(&window); } } int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) { return SetupWindowsDirect2DRenderer(); } ``` **UnitTestSnapshotViewerAppWindow** and **UnitTestSnapshotViewerViewModel** are defined in **GacUI.UnitTest.UI**.

View File

@@ -0,0 +1,12 @@
# Using Unit Test Framework
The most simple way to start the unit test framework would be: ```c++ #include <Vlpp.h> using namespace vl; using namespace vl::unittest; #ifdef VCZH_MSVC int wmain(int argc , wchar_t* argv[]) #else int main(int argc, char** argv) #endif { int result = unittest::UnitTest::RunAndDisposeTests(argc, argv); vl::FinalizeGlobalStorage(); #ifdef VCZH_CHECK_MEMORY_LEAKS _CrtDumpMemoryLeaks(); #endif return result; } ```
In this way you could pass **/D**, **/R**, **/C** and **/F** directly to your unit test binary and they will be handled by the unit test framework properly.
You can create multiple cpp files for your test cases, **/F** will automatically react to them. A typical test file would be: ```c++ #include <Vlpp.h> using namespace vl; using namespace vl::unittest; void AnError() { CHECK_FAIL(L"Not Implemented!"); } void AnException() { throw Exception(L"A message"); } TEST_FILE { TEST_CASE(L"This is a test case") { TEST_ASSERT(1 + 1 == 2); }); TEST_CATEGORY(L"This is a test category") { TEST_CASE(L"This is another test case") { TEST_ERROR(AnError()); TEST_EXCEPTION(AnException(), Exception, [](const Exception& e) { TEST_ASSERT(e.Message() == L"A message"); }); }); }); } ``` - **TEST_ASSERT(condition);** accepts an expression and fails if it evaluates to false. - **TEST_ERROR(statement);** accepts a statement and fails if it does not throw an exception is or inheriting from **Error**. Such error are usually triggered by **CHECK_ERROR** and **CHECK_FAIL**. - **TEST_EXCEPTION(statement,exception,handler);** executes the statement, fails if it does not throw an exception is or inheriting from **exception**. The handler will be called on that exception for additional assertion. Please be aware that these are macros, commas may be interepreted unexpectedly.
**TEST_CATEGORY** could be used to group other **TEST_CATEGORY** and **TEST_CASE**. **TEST_ASSERT**, **TEST_ERROR** and **TEST_EXCEPTION** can only be used in **TEST_CASE**. **TEST_CASE_ASSERT(condition);** is a shortcut for a **TEST_CASE** containing only a **TEST_ASSERT**.
If you get **TestA.cpp**, **TestB.cpp** and **TestC.cpp**, using no **/F** will run tests in all three files, using **/F:Test*.cpp** will run tests in all three files, using **/F:TestA.cpp /F:TestB.cpp** will run tests in **TestA.cpp** and **TestB.cpp** only.

View File

@@ -1,3 +1,7 @@
Copilot_Execution.md
Copilot_Planning.md
Copilot_Task.md
Build.log
Build.log.unfinished
Execute.log
Execute.log.unfinished

View File

@@ -19,8 +19,12 @@
<None Include="$(MSBuildThisFileDirectory)Copilot_Task.md" />
<None Include="$(MSBuildThisFileDirectory)Copilot_Scrum.md" />
<None Include="$(MSBuildThisFileDirectory).gitignore" />
<None Include="$(MSBuildThisFileDirectory)copilotShared.ps1" />
<None Include="$(MSBuildThisFileDirectory)copilotPrepare.ps1" />
<None Include="$(MSBuildThisFileDirectory)copilotExecute.ps1" />
<None Include="$(MSBuildThisFileDirectory)copilotBuild.ps1" />
<None Include="$(MSBuildThisFileDirectory)copilotDebug_Start.ps1" />
<None Include="$(MSBuildThisFileDirectory)copilotDebug_Stop.ps1" />
<None Include="$(MSBuildThisFileDirectory)copilotDebug_RunCommand.ps1" />
</ItemGroup>
</Project>

View File

@@ -1,20 +1,15 @@
. $PSScriptRoot\copilotShared.ps1
# Remove log file if it exists
$logFile = "$PSScriptRoot\Build.log"
if (Test-Path $logFile) {
Remove-Item $logFile -Force
Write-Host "Removed existing log file: $logFile"
}
# Find the solution folder by looking for *.sln files
$currentDir = Get-Location
$solutionFile = $null
while ($currentDir -ne $null) {
$solutionFiles = Get-ChildItem -Path $currentDir.Path -Filter "*.sln" -ErrorAction SilentlyContinue
if ($solutionFiles.Count -gt 0) {
$solutionFile = "$($currentDir.Path)\$($solutionFiles[0])"
Write-Host "Found solution file: $solutionFile"
break
}
$currentDir = $currentDir.Parent
}
if ($solutionFile -eq $null) {
throw "Could not find a solution file (*.sln file) in current directory or any parent directories."
}
$solutionFolder = GetSolutionDir
$solutionFile = "$solutionFolder\$((Get-ChildItem -Path $solutionFolder -Filter "*.sln" -ErrorAction SilentlyContinue)[0])"
$vsdevcmd = $env:VLPP_VSDEVCMD_PATH
if ($vsdevcmd -eq $null) {
@@ -28,12 +23,10 @@ $Platform = "x64"
$msbuild_arguments = "MSBUILD `"$solutionFile`" /m:8 $rebuildControl /p:Configuration=`"$Configuration`";Platform=`"$Platform`""
$cmd_arguments = "`"`"$vsdevcmd`" & $msbuild_arguments"
# Execute msbuild
# Execute msbuild with output to both console and log file
$logFile = "$PSScriptRoot\Build.log"
$commandLine = "/c $cmd_arguments"
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $env:ComSpec
$startInfo.Arguments = $commandLine
$startInfo.UseShellExecute = $false
$process = [System.Diagnostics.Process]::Start($startInfo)
$process.WaitForExit()
exit $process.ExitCode
$logFileUnfinished = "$logFile.unfinished"
& $env:ComSpec $commandLine 2>&1 | Tee-Object -FilePath $logFileUnfinished
Rename-Item -Path $logFileUnfinished -NewName $logFile -Force
exit $LASTEXITCODE

View File

@@ -0,0 +1,4 @@
.lines
l+s
l+t
.nvload "C:\Program Files\Microsoft Visual Studio\18\Community\Common7\Packages\Debugger\Visualizers\vlpp.natvis"

View File

@@ -0,0 +1,10 @@
param(
[Parameter(Mandatory=$true)]
[string]$Command
)
. $PSScriptRoot\copilotShared.ps1
$cdbpath = GetCDBPath
$commandLine = "echo .remote_exit | `"$($cdbpath)`" -remote npipe:server=.,pipe=VlppUnitTest -clines 0 -c `"$Command`""
echo $commandLine
cmd.exe /S /C $commandLine

27
.github/TaskLogs/copilotDebug_Start.ps1 vendored Normal file
View File

@@ -0,0 +1,27 @@
param(
[Parameter(Mandatory=$true)]
[string]$Executable
)
. $PSScriptRoot\copilotShared.ps1
# Ensure the executable name has .exe extension
if ($Executable.EndsWith(".exe")) {
throw "\$Executable parameter should not include the .exe extension: $Executable"
}
$executableName = $Executable + ".exe"
# Find the solution folder by looking for *.sln files
$solutionFolder = GetSolutionDir
# Find the file with the latest modification time
$latestFile = GetLatestModifiedExecutable $solutionFolder $executableName
Write-Host "Selected $executableName`: $($latestFile.Path) (Modified: $($latestFile.LastWriteTime))"
# Try to read debug arguments from the corresponding .vcxproj.user file
$debugArgs = GetDebugArgs $solutionFolder $latestFile $Executable
$cdbpath = GetCDBPath
$commandLine = "`"$($cdbpath)`" -server npipe:pipe=VlppUnitTest -cf `"$PSScriptRoot\copilotDebug_Init.txt`" -o `"$($latestFile.Path)`" $debugArgs"
echo $commandLine
cmd.exe /S /C $commandLine

View File

@@ -0,0 +1,5 @@
. $PSScriptRoot\copilotShared.ps1
$cdbpath = GetCDBPath
$commandLine = "`"$($cdbpath)`" -remote npipe:server=.,pipe=VlppUnitTest -clines 0 -c `"qq`""
echo $commandLine
cmd.exe /S /C $commandLine

View File

@@ -3,95 +3,35 @@ param(
[string]$Executable
)
# Ensure the executable name has .exe extension
if (-not $Executable.EndsWith(".exe")) {
$executableName = $Executable + ".exe"
} else {
$executableName = $Executable
. $PSScriptRoot\copilotShared.ps1
# Remove log file if it exists
$logFile = "$PSScriptRoot\Execute.log"
if (Test-Path $logFile) {
Remove-Item $logFile -Force
Write-Host "Removed existing log file: $logFile"
}
# Ensure the executable name has .exe extension
if ($Executable.EndsWith(".exe")) {
throw "\$Executable parameter should not include the .exe extension: $Executable"
}
$executableName = $Executable + ".exe"
# Find the solution folder by looking for *.sln files
$currentDir = Get-Location
$solutionFolder = $null
while ($currentDir -ne $null) {
$solutionFiles = Get-ChildItem -Path $currentDir.Path -Filter "*.sln" -ErrorAction SilentlyContinue
if ($solutionFiles.Count -gt 0) {
$solutionFolder = $currentDir.Path
Write-Host "Found solution folder: $solutionFolder"
break
}
$currentDir = $currentDir.Parent
}
if ($solutionFolder -eq $null) {
throw "Could not find a solution folder (*.sln file) in current directory or any parent directories."
}
# Define configuration to path mappings
$configToPathMap = @{
"Debug|Win32" = "$solutionFolder\Debug\$executableName"
"Release|Win32" = "$solutionFolder\Release\$executableName"
"Debug|x64" = "$solutionFolder\x64\Debug\$executableName"
"Release|x64" = "$solutionFolder\x64\Release\$executableName"
}
# Find existing files and get their modification times with configuration info
$existingFiles = @()
foreach ($config in $configToPathMap.Keys) {
$path = $configToPathMap[$config]
if (Test-Path $path) {
$fileInfo = Get-Item $path
$existingFiles += [PSCustomObject]@{
Path = $path
Configuration = $config
LastWriteTime = $fileInfo.LastWriteTime
}
}
}
$solutionFolder = GetSolutionDir
# Find the file with the latest modification time
if ($existingFiles.Count -gt 0) {
$latestFile = $existingFiles | Sort-Object LastWriteTime -Descending | Select-Object -First 1
Write-Host "Selected $executableName`: $($latestFile.Path) (Modified: $($latestFile.LastWriteTime))"
$latestFile = GetLatestModifiedExecutable $solutionFolder $executableName
Write-Host "Selected $executableName`: $($latestFile.Path) (Modified: $($latestFile.LastWriteTime))"
# Try to read debug arguments from the corresponding .vcxproj.user file
$userProjectFile = "$solutionFolder\$Executable\$Executable.vcxproj.user"
$debugArgs = ""
# Try to read debug arguments from the corresponding .vcxproj.user file
$debugArgs = GetDebugArgs $solutionFolder $latestFile $Executable
if (Test-Path $userProjectFile) {
try {
[xml]$userProjectXml = Get-Content $userProjectFile
$configPlatform = $latestFile.Configuration
if ($configPlatform) {
# Find the PropertyGroup with the matching Condition
$propertyGroup = $userProjectXml.Project.PropertyGroup | Where-Object {
$_.Condition -eq "'`$(Configuration)|`$(Platform)'=='$configPlatform'"
}
if ($propertyGroup -and $propertyGroup.LocalDebuggerCommandArguments) {
$debugArgs = $propertyGroup.LocalDebuggerCommandArguments
Write-Host "Found debug arguments: $debugArgs"
}
}
}
catch {
Write-Host "Warning: Could not read debug arguments from $userProjectFile"
}
} else {
Write-Host "Failed to find $userProjectFile"
}
# Execute the selected executable with debug arguments using cmd to handle argument parsing
$commandLine = "/C $debugArgs"
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $latestFile.Path
$startInfo.Arguments = $commandLine
$startInfo.UseShellExecute = $false
$process = [System.Diagnostics.Process]::Start($startInfo)
$process.WaitForExit()
exit $process.ExitCode
} else {
throw "No $executableName files found in any of the expected locations."
}
# Execute the selected executable with debug arguments and save output to log file
$logFile = "$PSScriptRoot\Execute.log"
$logFileUnfinished = "$logFile.unfinished"
$commandLine = "`"$($latestFile.Path)`" /C $debugArgs"
& { $commandLine; & cmd.exe /S /C $commandLine 2>&1 } | Tee-Object -FilePath $logFileUnfinished
Rename-Item -Path $logFileUnfinished -NewName $logFile -Force
exit $LASTEXITCODE

88
.github/TaskLogs/copilotShared.ps1 vendored Normal file
View File

@@ -0,0 +1,88 @@
function GetCDBPath {
if ($env:CDBPATH -eq $null) {
$MESSAGE_1 = "You have to add an environment variable named CDBPATH and set its value to the path of cdb.exe, e.g.:"
$MESSAGE_2 = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
$MESSAGE_3 = "To get cdb.exe, you need to install WDK via VS Installer (Individual Components page) followed by Windows WDK."
throw "$MESSAGE_1\r\n$MESSAGE_2\r\n$MESSAGE_3"
}
return $env:CDBPATH
}
function GetSolutionDir {
$currentDir = Get-Location
$solutionFolder = $null
while ($currentDir -ne $null) {
$solutionFiles = Get-ChildItem -Path $currentDir.Path -Filter "*.sln" -ErrorAction SilentlyContinue
if ($solutionFiles.Count -gt 0) {
$solutionFolder = $currentDir.Path
Write-Host "Found solution folder: $solutionFolder"
break
}
$currentDir = $currentDir.Parent
}
if ($solutionFolder -eq $null) {
throw "Could not find a solution folder (*.sln file) in current directory or any parent directories."
}
return $solutionFolder
}
function GetLatestModifiedExecutable($solutionFolder, $executableName) {
# Define configuration to path mappings
$configToPathMap = @{
"Debug|Win32" = "$solutionFolder\Debug\$executableName"
"Release|Win32" = "$solutionFolder\Release\$executableName"
"Debug|x64" = "$solutionFolder\x64\Debug\$executableName"
"Release|x64" = "$solutionFolder\x64\Release\$executableName"
}
# Find existing files and get their modification times with configuration info
$existingFiles = @()
foreach ($config in $configToPathMap.Keys) {
$path = $configToPathMap[$config]
if (Test-Path $path) {
$fileInfo = Get-Item $path
$existingFiles += [PSCustomObject]@{
Path = $path
Configuration = $config
LastWriteTime = $fileInfo.LastWriteTime
}
}
}
if ($existingFiles.Count -eq 0) {
throw "No $executableName files found in any of the expected locations."
}
return $existingFiles | Sort-Object LastWriteTime -Descending | Select-Object -First 1
}
function GetDebugArgs($solutionFolder, $latestFile, $executable) {
$userProjectFile = "$solutionFolder\$executable\$executable.vcxproj.user"
if (Test-Path $userProjectFile) {
try {
[xml]$userProjectXml = Get-Content $userProjectFile
$configPlatform = $latestFile.Configuration
if ($configPlatform) {
# Find the PropertyGroup with the matching Condition
$propertyGroup = $userProjectXml.Project.PropertyGroup | Where-Object {
$_.Condition -eq "'`$(Configuration)|`$(Platform)'=='$configPlatform'"
}
if ($propertyGroup -and $propertyGroup.LocalDebuggerCommandArguments) {
$debugArgs = $propertyGroup.LocalDebuggerCommandArguments
Write-Host "Found debug arguments: $debugArgs"
return $debugArgs
}
}
}
catch {
Write-Host "Warning: Could not read debug arguments from $userProjectFile"
}
} else {
Write-Host "Failed to find $userProjectFile"
}
return ""
}

View File

@@ -43,7 +43,7 @@ This project is built on top of:
- DO NOT MODIFY any source code in the `Import` folder, they are dependencies.
- DO NOT MODIFY any source code in the `Release` folder, they are generated release files.
- You can modify source code in the `Source` and `Test` folder.
- Use tabs for indentation in C++ source code.
- Use tabs for indentation in C++ source code. For JSON or XML in a string literal, use double spaces.
- Header files are guarded with macros instead of `#pragma once`.
- Use `auto` to define variables if it is doable. Use `auto&&` when the type is big or when it is a collection type.
- In header files, do not use `using namespace` statement, full name of types are always required. In a class/struct/union declaration, member names must be aligned in the same column at least in the same public, protected or private session. Please follow this coding style just like other part of the code.

View File

@@ -139,6 +139,7 @@ I made important updates to the source code manually during the execution of the
- Read through `Copilot_Execution.md`. There may be some fixing attempts, that were done by you.
- Compare existing source code with `Copilot_Execution.md`, finding what is changed.
- Don't rely on `git` to identify changes, as I always commit them periodaically. You need to compare the actual source code with `Copilot_Execution.md`.
- During comparing, you need to take into consideration of the fixing attempts, as sometimes you didn't update the main content of the document.
- Identify all differences between the document and the source code:
- If it is caused by any fixing attempts, ignore it.
@@ -175,10 +176,6 @@ I made important updates to the source code manually during the execution of the
- If you created `Copilot_Execution_Finding.md`, in Step 6.3:
- In the backup folder, there will be a copied `Copilot_Execution.md`, append all contents from `Copilot_Execution_Finding.md` to the end of it.
- Delete the `Copilot_Execution_Finding.md` you created.
- Run `git status` to make sure you have manipulate document files correctly:
- `Copilot_Scrum.md` is modified, unless nothing needs to be changed.
- `Copilot_Task.md`, `Copilot_Planning.md` and `Copilot_Execution.md` are created in a backup folder.
- There is no `Copilot_Execution_Finding.md`.
# External Tools Environment and Context
@@ -201,6 +198,9 @@ This guidance is for accessing following files mentioned in this instruction:
- `copilotPrepare.ps1`
- `copilotBuild.ps1`
- `copilotExecute.ps1`
- `copilotDebug_Start.ps1`
- `copilotDebug_Stop.ps1`
- `copilotDebug_RunCommand.ps1`
- `Build.log`
- `Execute.log`

View File

@@ -103,6 +103,9 @@ This guidance is for accessing following files mentioned in this instruction:
- `copilotPrepare.ps1`
- `copilotBuild.ps1`
- `copilotExecute.ps1`
- `copilotDebug_Start.ps1`
- `copilotDebug_Stop.ps1`
- `copilotDebug_RunCommand.ps1`
- `Build.log`
- `Execute.log`

View File

@@ -97,6 +97,9 @@ This guidance is for accessing following files mentioned in this instruction:
- `copilotPrepare.ps1`
- `copilotBuild.ps1`
- `copilotExecute.ps1`
- `copilotDebug_Start.ps1`
- `copilotDebug_Stop.ps1`
- `copilotDebug_RunCommand.ps1`
- `Build.log`
- `Execute.log`

View File

@@ -94,6 +94,9 @@ This guidance is for accessing following files mentioned in this instruction:
- `copilotPrepare.ps1`
- `copilotBuild.ps1`
- `copilotExecute.ps1`
- `copilotDebug_Start.ps1`
- `copilotDebug_Stop.ps1`
- `copilotDebug_RunCommand.ps1`
- `Build.log`
- `Execute.log`

View File

@@ -98,6 +98,9 @@ This guidance is for accessing following files mentioned in this instruction:
- `copilotPrepare.ps1`
- `copilotBuild.ps1`
- `copilotExecute.ps1`
- `copilotDebug_Start.ps1`
- `copilotDebug_Stop.ps1`
- `copilotDebug_RunCommand.ps1`
- `Build.log`
- `Execute.log`
@@ -150,9 +153,85 @@ You will find the `TaskLogs` project in the current solution, which should conta
- "Passed test cases: Y/Y"
- DO NOT TRUST related tools Visual Studio Code offers you, like `get_errors` or `get_task_output`, etc.
## Debugging Unit Test
Debugging would be useful when you lack of necessary information.
In this section I offer you a set of powershell scripts that work with CDB (Microsoft's Console Debugger).
CDB accepts exactly same commands as WinDBG.
### Start a Debugger
`REPO-ROOT` is the root folder of the repo.
`SOLUTION-ROOT` is the folder of the solution (*.sln) file where the unit test project is in.
Find out `Unit Test Project Structure` to understand the solution folder and the unit test project name you are working with.
Additional information could be found in THE FIRST LINE in `REPO-ROOT/.github/TaskLogs/Execute.log`.
Execute the following powershell commands:
```
cd SOLUTION-ROOT
start powershell {& REPO-ROOT\.github\TaskLogs\copilotDebug_Start.ps1 -Executable PROJECT-NAME}
```
The `start powershell {}` is necessary otherwise the script will block the execution forever causing you to wait infinitely.
The script will finish immediately, leaving a debugger running in the background. You can send commands to the debugger.
The process being debugged is paused at the beginning, you are given a chance to set break-points.
After you are prepared, send the `g` command to start running.
### Stop a Debugger
You must call this script do stop the debugger.
Do not stop the debugger using any command.
This script is also required to run before compiling only when Visual Studio Code tasks are not available to you.
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_Stop.ps1
```
If there is any error message, it means the debugger is not alive, it is good.
### Sending Commands to Debugger
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_RunCommand.ps1 -Command "Commands"
```
The effect of commands lasts across multiple `copilotDebug_RunCommand.ps1` calls. For example, after you executed `.frame X`, you do not need to repeat it to use `dx` under the same call stack frame in later calls, as `.frame X` already effective.
Multiple commands can be executed sequentially separated by ";".
The debugger is configured to be using source mode, which means you can see source files and line numbers in the call stack, and step in/out/over are working line by line.
CDB accepts exactly same commands as WinDBG, and here are some recommended commands:
- **g**: continue until hitting a break-point or crashing.
- **k**n: print current call stack.
- **kn LINES**: print first `LINES` of the current call stack.
- **.frame NUMBER**: inspect the call stack frame labeled with `NUMBER`. `kn` will show the number, file and line along with the call stack.
- **dv**: list all available variables in the current call stack frame.
- **dx EXPRESSION**: evaluate the `EXPRESSION` and print the result. `EXPRESSION` can be any valid C programming language expression. When you specify a type (especially when doing casting), full namespaces are required, do not start with `::`.
- **bp `FILE:LINE`**: set a break-point at the specified line in `FILE`, starting from 0. A pair of "`" characters are required around the target, this is not a markdown syntax.
- **bl**, **.bpcmds**, **be NUMBERS**, **bd NUMBERS**, **bc NUMBERS**, **bsc NUMBER CONDITION**: list, list with attached commands, enable, disable, delete, attach a command to break-point(s).
- **p**: step over, aka execute the complete current line.
- **t**: step in, aka execute the currrent line, if any function is called, goes into the function.
- **pt**: step out, aka run until the end of the current function.
An `.natvis` file is automatically offered with the debugger,
it formats some primitive types defined in the `Vlpp` project,
including `WString` and other string types, `Nullable`, `Variant`, container types, etc.
The formmating applies to the **dx** command,
when you want to see raw data instead of formatting printing,
use **dx (EXPRESSION),!**.
You can also use `dv -rX` to expand "X" levels of fields, the default option is `-r0` which only expand one level of fields.
### Commands to Avoid
- Only use **dv** without any parameters.
- DO NOT use **dt**.
- DO NOT use **q**, **qd**, **qq**, **qqd** etc to stop the debugger, always use `copilotDebug_Stop.ps1`.
## Understanding the Building Tools
**WARNING**: Information offered in this section is for background knowledge only. You should always run `Build Unit Tests` and `Run Unit Tests` instead of running these scripts or calling msbuild or other executable by yourself.
**WARNING**: Information offered in this section is for background knowledge only.
You should always run `Build Unit Tests` and `Run Unit Tests` instead of running these scripts or calling msbuild or other executable by yourself.
Only when you cannot access tools offered by Visual Studio Code, scripts below are allowed to use.
`REPO-ROOT` is the root folder of the repo.
`SOLUTION-ROOT` is the folder containing the solution file.
@@ -161,6 +240,13 @@ You will find the `TaskLogs` project in the current solution, which should conta
When verifying test projects on Windows, msbuild is used to build a solution (`*.sln`) file.
A solution contains many project (`*.vcxproj`) files, a project generates an executable (`*.exe`) file.
Before building, ensure the debugger has stopped, otherwise the running unit test process will cause a linking failure.
If there is any error message, it means the debugger is not alive, it is good.
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_Stop.ps1
```
The `Build Unit Tests` task calls msbuild to build the only solution which contains all test cases.
Inside the task, it runs `copilotBuild.ps1`

View File

@@ -53,7 +53,7 @@
- The main agent should call different sub agent for each test-fix process.
- Do not test and retrieve test results in the main agent.
### Use a sub agent to run the following instructions (`Execute Unit Test` and `Fix Failed Test Cases`)
### Use a sub agent to run the following instructions (`Execute Unit Test`, `Identify the Cause of Failure` and `Fix Failed Test Cases`)
#### Execute Unit Test
@@ -65,13 +65,23 @@
- In other source code, `vl::console::Console::WriteLine` would help. In `Vlpp` project, you should `#include` `Console.h`. In other projects, the `Console` class should just be available.
- When added logging are not longer necessary, you should remove all of them.
#### Identify the Cause of Failure
- You can refer to `Copilot_Task.md` and `Copilot_Planning.md` to understand the context, keep the target unchanged.
- Dig into related source code carefully, make your assumption about the root cause.
- `TEST_ASSERT` in test cases or `vl::console::Console::WriteLine` in the source code would help.
- They can make sure the expected code path is executed.
- They can print variable values after converting to strings.
- Debug the unit test directly to get accurate clues if you are not confident of the assumption
- Follow `Debugging Unit Test` to start a debugger and run WinDBG commands.
- From there you can set break-points, walk through code by lines, and inspect variables.
- You must stop the debugger after you finish debugging.
- When you have made a few guess but did not progress, you are recommended to debug the unit test directly.
- Break-points are very useful to ensure the expected code path is executed, and you can inspect variable values.
#### Fix Failed Test Cases
- If there are failed test cases, fix the code to make it work.
- If your change did not change the test result, make sure you followed `Step 2. Compile` to compile the code.
- If the test result still not changed after redoing `Step 2. Compile` and `Step 3. Run Unit Test`, these two steps are absolutely no problem, the only reason is that your change is not correct.
- You must carefully identify, if the cause is in the source code or in the failed test. In most of the cases, the cause is in the source code.
- You can reference to `Copilot_Task.md` and `Copilot_Planning.md` for more details before making a decision, about fixing the test case or the source code.
- Apply fixings to source files.
- DO NOT delete any test case.
- For every attempt of fixing the source code:
- Explain why the original change did not work.
@@ -79,6 +89,7 @@
- Explain why you think it would solve the build break or test break.
- Log these in `Copilot_Execution.md`, with section `## Fixing attempt No.<attempt_number>` in `# FIXING ATTEMPTS`.
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Compile`
- `Step 2. Compile` and `Step 3. Run Unit Test` are absolutely no problem. If you didn't see any progress, the only reason is that your change is not correct.
## Step 4. Check it Again
@@ -105,6 +116,9 @@ This guidance is for accessing following files mentioned in this instruction:
- `copilotPrepare.ps1`
- `copilotBuild.ps1`
- `copilotExecute.ps1`
- `copilotDebug_Start.ps1`
- `copilotDebug_Stop.ps1`
- `copilotDebug_RunCommand.ps1`
- `Build.log`
- `Execute.log`
@@ -157,9 +171,85 @@ You will find the `TaskLogs` project in the current solution, which should conta
- "Passed test cases: Y/Y"
- DO NOT TRUST related tools Visual Studio Code offers you, like `get_errors` or `get_task_output`, etc.
## Debugging Unit Test
Debugging would be useful when you lack of necessary information.
In this section I offer you a set of powershell scripts that work with CDB (Microsoft's Console Debugger).
CDB accepts exactly same commands as WinDBG.
### Start a Debugger
`REPO-ROOT` is the root folder of the repo.
`SOLUTION-ROOT` is the folder of the solution (*.sln) file where the unit test project is in.
Find out `Unit Test Project Structure` to understand the solution folder and the unit test project name you are working with.
Additional information could be found in THE FIRST LINE in `REPO-ROOT/.github/TaskLogs/Execute.log`.
Execute the following powershell commands:
```
cd SOLUTION-ROOT
start powershell {& REPO-ROOT\.github\TaskLogs\copilotDebug_Start.ps1 -Executable PROJECT-NAME}
```
The `start powershell {}` is necessary otherwise the script will block the execution forever causing you to wait infinitely.
The script will finish immediately, leaving a debugger running in the background. You can send commands to the debugger.
The process being debugged is paused at the beginning, you are given a chance to set break-points.
After you are prepared, send the `g` command to start running.
### Stop a Debugger
You must call this script do stop the debugger.
Do not stop the debugger using any command.
This script is also required to run before compiling only when Visual Studio Code tasks are not available to you.
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_Stop.ps1
```
If there is any error message, it means the debugger is not alive, it is good.
### Sending Commands to Debugger
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_RunCommand.ps1 -Command "Commands"
```
The effect of commands lasts across multiple `copilotDebug_RunCommand.ps1` calls. For example, after you executed `.frame X`, you do not need to repeat it to use `dx` under the same call stack frame in later calls, as `.frame X` already effective.
Multiple commands can be executed sequentially separated by ";".
The debugger is configured to be using source mode, which means you can see source files and line numbers in the call stack, and step in/out/over are working line by line.
CDB accepts exactly same commands as WinDBG, and here are some recommended commands:
- **g**: continue until hitting a break-point or crashing.
- **k**n: print current call stack.
- **kn LINES**: print first `LINES` of the current call stack.
- **.frame NUMBER**: inspect the call stack frame labeled with `NUMBER`. `kn` will show the number, file and line along with the call stack.
- **dv**: list all available variables in the current call stack frame.
- **dx EXPRESSION**: evaluate the `EXPRESSION` and print the result. `EXPRESSION` can be any valid C programming language expression. When you specify a type (especially when doing casting), full namespaces are required, do not start with `::`.
- **bp `FILE:LINE`**: set a break-point at the specified line in `FILE`, starting from 0. A pair of "`" characters are required around the target, this is not a markdown syntax.
- **bl**, **.bpcmds**, **be NUMBERS**, **bd NUMBERS**, **bc NUMBERS**, **bsc NUMBER CONDITION**: list, list with attached commands, enable, disable, delete, attach a command to break-point(s).
- **p**: step over, aka execute the complete current line.
- **t**: step in, aka execute the currrent line, if any function is called, goes into the function.
- **pt**: step out, aka run until the end of the current function.
An `.natvis` file is automatically offered with the debugger,
it formats some primitive types defined in the `Vlpp` project,
including `WString` and other string types, `Nullable`, `Variant`, container types, etc.
The formmating applies to the **dx** command,
when you want to see raw data instead of formatting printing,
use **dx (EXPRESSION),!**.
You can also use `dv -rX` to expand "X" levels of fields, the default option is `-r0` which only expand one level of fields.
### Commands to Avoid
- Only use **dv** without any parameters.
- DO NOT use **dt**.
- DO NOT use **q**, **qd**, **qq**, **qqd** etc to stop the debugger, always use `copilotDebug_Stop.ps1`.
## Understanding the Building Tools
**WARNING**: Information offered in this section is for background knowledge only. You should always run `Build Unit Tests` and `Run Unit Tests` instead of running these scripts or calling msbuild or other executable by yourself.
**WARNING**: Information offered in this section is for background knowledge only.
You should always run `Build Unit Tests` and `Run Unit Tests` instead of running these scripts or calling msbuild or other executable by yourself.
Only when you cannot access tools offered by Visual Studio Code, scripts below are allowed to use.
`REPO-ROOT` is the root folder of the repo.
`SOLUTION-ROOT` is the folder containing the solution file.
@@ -168,6 +258,13 @@ You will find the `TaskLogs` project in the current solution, which should conta
When verifying test projects on Windows, msbuild is used to build a solution (`*.sln`) file.
A solution contains many project (`*.vcxproj`) files, a project generates an executable (`*.exe`) file.
Before building, ensure the debugger has stopped, otherwise the running unit test process will cause a linking failure.
If there is any error message, it means the debugger is not alive, it is good.
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_Stop.ps1
```
The `Build Unit Tests` task calls msbuild to build the only solution which contains all test cases.
Inside the task, it runs `copilotBuild.ps1`

View File

@@ -41,7 +41,7 @@
- The main agent should call different sub agent for each test-fix process.
- Do not test and retrieve test results in the main agent.
### Use a sub agent to run the following instructions (`Execute Unit Test` and `Fix Failed Test Cases`)
### Use a sub agent to run the following instructions (`Execute Unit Test`, `Identify the Cause of Failure` and `Fix Failed Test Cases`)
#### Execute Unit Test
@@ -53,14 +53,25 @@
- In other source code, `vl::console::Console::WriteLine` would help. In `Vlpp` project, you should `#include` `Console.h`. In other projects, the `Console` class should just be available.
- When added logging are not longer necessary, you should remove all of them.
#### Identify the Cause of Failure
- Dig into related source code carefully, make your assumption about the root cause.
- `TEST_ASSERT` in test cases or `vl::console::Console::WriteLine` in the source code would help.
- They can make sure the expected code path is executed.
- They can print variable values after converting to strings.
- Debug the unit test directly to get accurate clues if you are not confident of the assumption
- Follow `Debugging Unit Test` to start a debugger and run WinDBG commands.
- From there you can set break-points, walk through code by lines, and inspect variables.
- You must stop the debugger after you finish debugging.
- When you have made a few guess but did not progress, you are recommended to debug the unit test directly.
- Break-points are very useful to ensure the expected code path is executed, and you can inspect variable values.
#### Fix Failed Test Cases
- If there are failed test cases, fix the code to make it work.
- If your change did not change the test result, make sure you followed `Step 2. Compile` to compile the code.
- If the test result still not changed after redoing `Step 2. Compile` and `Step 3. Run Unit Test`, these two steps are absolutely no problem, the only reason is that your change is not correct.
- You must carefully identify, if the cause is in the source code or in the failed test. In most of the cases, the cause is in the source code.
- Apply fixings to source files.
- DO NOT delete any test case.
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Compile`
- `Step 2. Compile` and `Step 3. Run Unit Test` are absolutely no problem. If you didn't see any progress, the only reason is that your change is not correct.
## Step 4. Check it Again
@@ -111,9 +122,85 @@
- "Passed test cases: Y/Y"
- DO NOT TRUST related tools Visual Studio Code offers you, like `get_errors` or `get_task_output`, etc.
## Debugging Unit Test
Debugging would be useful when you lack of necessary information.
In this section I offer you a set of powershell scripts that work with CDB (Microsoft's Console Debugger).
CDB accepts exactly same commands as WinDBG.
### Start a Debugger
`REPO-ROOT` is the root folder of the repo.
`SOLUTION-ROOT` is the folder of the solution (*.sln) file where the unit test project is in.
Find out `Unit Test Project Structure` to understand the solution folder and the unit test project name you are working with.
Additional information could be found in THE FIRST LINE in `REPO-ROOT/.github/TaskLogs/Execute.log`.
Execute the following powershell commands:
```
cd SOLUTION-ROOT
start powershell {& REPO-ROOT\.github\TaskLogs\copilotDebug_Start.ps1 -Executable PROJECT-NAME}
```
The `start powershell {}` is necessary otherwise the script will block the execution forever causing you to wait infinitely.
The script will finish immediately, leaving a debugger running in the background. You can send commands to the debugger.
The process being debugged is paused at the beginning, you are given a chance to set break-points.
After you are prepared, send the `g` command to start running.
### Stop a Debugger
You must call this script do stop the debugger.
Do not stop the debugger using any command.
This script is also required to run before compiling only when Visual Studio Code tasks are not available to you.
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_Stop.ps1
```
If there is any error message, it means the debugger is not alive, it is good.
### Sending Commands to Debugger
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_RunCommand.ps1 -Command "Commands"
```
The effect of commands lasts across multiple `copilotDebug_RunCommand.ps1` calls. For example, after you executed `.frame X`, you do not need to repeat it to use `dx` under the same call stack frame in later calls, as `.frame X` already effective.
Multiple commands can be executed sequentially separated by ";".
The debugger is configured to be using source mode, which means you can see source files and line numbers in the call stack, and step in/out/over are working line by line.
CDB accepts exactly same commands as WinDBG, and here are some recommended commands:
- **g**: continue until hitting a break-point or crashing.
- **k**n: print current call stack.
- **kn LINES**: print first `LINES` of the current call stack.
- **.frame NUMBER**: inspect the call stack frame labeled with `NUMBER`. `kn` will show the number, file and line along with the call stack.
- **dv**: list all available variables in the current call stack frame.
- **dx EXPRESSION**: evaluate the `EXPRESSION` and print the result. `EXPRESSION` can be any valid C programming language expression. When you specify a type (especially when doing casting), full namespaces are required, do not start with `::`.
- **bp `FILE:LINE`**: set a break-point at the specified line in `FILE`, starting from 0. A pair of "`" characters are required around the target, this is not a markdown syntax.
- **bl**, **.bpcmds**, **be NUMBERS**, **bd NUMBERS**, **bc NUMBERS**, **bsc NUMBER CONDITION**: list, list with attached commands, enable, disable, delete, attach a command to break-point(s).
- **p**: step over, aka execute the complete current line.
- **t**: step in, aka execute the currrent line, if any function is called, goes into the function.
- **pt**: step out, aka run until the end of the current function.
An `.natvis` file is automatically offered with the debugger,
it formats some primitive types defined in the `Vlpp` project,
including `WString` and other string types, `Nullable`, `Variant`, container types, etc.
The formmating applies to the **dx** command,
when you want to see raw data instead of formatting printing,
use **dx (EXPRESSION),!**.
You can also use `dv -rX` to expand "X" levels of fields, the default option is `-r0` which only expand one level of fields.
### Commands to Avoid
- Only use **dv** without any parameters.
- DO NOT use **dt**.
- DO NOT use **q**, **qd**, **qq**, **qqd** etc to stop the debugger, always use `copilotDebug_Stop.ps1`.
## Understanding the Building Tools
**WARNING**: Information offered in this section is for background knowledge only. You should always run `Build Unit Tests` and `Run Unit Tests` instead of running these scripts or calling msbuild or other executable by yourself.
**WARNING**: Information offered in this section is for background knowledge only.
You should always run `Build Unit Tests` and `Run Unit Tests` instead of running these scripts or calling msbuild or other executable by yourself.
Only when you cannot access tools offered by Visual Studio Code, scripts below are allowed to use.
`REPO-ROOT` is the root folder of the repo.
`SOLUTION-ROOT` is the folder containing the solution file.
@@ -122,6 +209,13 @@
When verifying test projects on Windows, msbuild is used to build a solution (`*.sln`) file.
A solution contains many project (`*.vcxproj`) files, a project generates an executable (`*.exe`) file.
Before building, ensure the debugger has stopped, otherwise the running unit test process will cause a linking failure.
If there is any error message, it means the debugger is not alive, it is good.
```
& REPO-ROOT\.github\TaskLogs\copilotDebug_Stop.ps1
```
The `Build Unit Tests` task calls msbuild to build the only solution which contains all test cases.
Inside the task, it runs `copilotBuild.ps1`

View File

@@ -123,6 +123,9 @@ This guidance is for accessing following files mentioned in this instruction:
- `copilotPrepare.ps1`
- `copilotBuild.ps1`
- `copilotExecute.ps1`
- `copilotDebug_Start.ps1`
- `copilotDebug_Stop.ps1`
- `copilotDebug_RunCommand.ps1`
- `Build.log`
- `Execute.log`