Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dotnet-package-skills/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ eng\common\dotnet.cmd artifacts\bin\DotnetPackageSkills\Release\net10.0\dotnet-p
The build creates an unsigned tool package under `artifacts\packages\Release\Shipping`.
It does not replace a globally installed tool. Only the official pipeline signs the package.

For manual tests on a personal Mac, see the [full terminal regression suite](tests/terminal/README.md).
These tests are not wired into CI.

## Layout

```
Expand Down
3 changes: 3 additions & 0 deletions dotnet-package-skills/tests/terminal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv/
__pycache__/
*.pyc
129 changes: 129 additions & 0 deletions dotnet-package-skills/tests/terminal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Manual terminal regressions

This is the full 51-case terminal suite, not a smoke test and not a CI job. It drives the
built `dotnet-package-skills` apphost through a real PTY, with `pyte` and
`@xterm/headless` checking rendering, colors, alternate buffers and resize reflow.
It also checks installs, removals, ownership, manifests, errors and cancellation against
isolated fake packages. **Actual macOS execution is still pending.** Linux PTY helper
results and Windows tool results are not a Mac pass.

## Set up on a Mac

Use Terminal, iTerm2 or another shell; no desktop automation is needed. Install:

- The .NET SDK selected by the **repository root** `global.json` (currently 10.0.401).
Its .NET 10 runtime is required even when testing the net8.0 apphost.
- The .NET 8 runtime in that same installation if also testing net8.0. The apphost
permits major roll-forward, so require runtime 8 below to actually test .NET 8.
- Python 3.9 or newer, and a supported Node.js LTS release with npm.

Use native arm64 SDK/runtime/Node/Python on Apple Silicon, or native x64 on Intel;
do not mix Rosetta x64 runtimes with an arm64 apphost. Run from this branch's checkout,
including when its path contains spaces:

```bash
cd "/path/to/Client.Tools"
repo="$PWD"
terminal="$repo/dotnet-package-skills/tests/terminal"

dotnet --version
dotnet --list-runtimes
export DOTNET_ROOT="$(python3 -c 'import os,shutil; print(os.path.dirname(os.path.realpath(shutil.which("dotnet"))))')"
export DOTNET_HOST_PATH="$DOTNET_ROOT/dotnet"

python3 -m venv "$terminal/.venv"
"$terminal/.venv/bin/python" -m pip install -r "$terminal/requirements.txt"
npm --prefix "$terminal" ci
```

Python dependencies are pinned; `pywinpty` is installed only on Windows. The POSIX
backend uses Python's standard library, not a new terminal dependency.

## Build and run the full suite

Build from the repository root with the selected SDK and the existing Arcade imports.
This project-scoped build produces both native apphosts without running or changing CI:

```bash
dotnet build "$repo/dotnet-package-skills/src/DotnetPackageSkills.csproj" \
--configuration Release
```

Alternatively, use the native root build entry point:
`bash eng/common/build.sh --restore --build --configuration Release --projects dotnet-package-skills/src/DotnetPackageSkills.csproj`.
If Arcade selects a checkout-local `.dotnet`, put that installation on `PATH` and set
`DOTNET_ROOT`/`DOTNET_HOST_PATH` to it for the tests; runtime 8 must also be available
there for the optional run. Do not use `--prepareMachine`.

The tool path is the executable **without `.exe`**, not the DLL, a Windows build copied
to the Mac, or a globally installed tool. Give each run a fresh artifact directory:

```bash
run="$(mktemp -d "${TMPDIR:-/tmp}/package-skills-terminal.XXXXXX")"
printf 'Artifacts: %s\n' "$run"
set -o pipefail

"$terminal/.venv/bin/python" "$terminal/verify_picker.py" \
--tool "$repo/artifacts/bin/DotnetPackageSkills/Release/net10.0/dotnet-package-skills" \
--artifacts "$run/net10.0" 2>&1 | tee "$run/net10.0.log"
```

Optional full run against .NET 8 (requires the SDK plus both runtimes listed above):

```bash
DOTNET_ROLL_FORWARD=LatestPatch \
"$terminal/.venv/bin/python" "$terminal/verify_picker.py" \
--tool "$repo/artifacts/bin/DotnetPackageSkills/Release/net8.0/dotnet-package-skills" \
--artifacts "$run/net8.0" 2>&1 | tee "$run/net8.0.log"
```

Exit 0 means all selected cases passed; a failure, timeout, missing dependency or invalid
apphost exits nonzero. `pipefail` keeps failures visible when using `tee`. A full run
selects 51 cases; do not report a filtered run as a full pass.
The console summary is saved in `net10.0.log` / `net8.0.log`; per-terminal `.ansi.txt`,
`.screen.txt` and (for reflow cases) `.buffers.json` files are under each run's `logs/`.
Keep the printed `$run` path and these files when reporting failures.

The tests create Demo.Alpha/Demo.Beta packages and a local-only feed. Restore/discovery
cases need the SDK but no remote package feeds at runtime. Two lock cases build the
small `mutex_holder.cs.txt` fixture into the artifact directory, using .NET's actual
named mutex rather than an unrelated POSIX file lock. Its `.txt` extension prevents
the regular C# test project from compiling this manual-only fixture.
Package caches, SDK user profiles, HTTP caches, skill destinations and handwritten
sentinels are test-owned. SDK first-run certificate creation, global-tool PATH updates
and workload update checks are disabled for fixtures. Tests never install/replace a global tool or remove the user's
skills/cache directories. Only their freshly created temporary fixture directories
are cleaned; logs and the mutex fixture remain in `$run`.

## Platform coverage and focused checks

The driver selects all 51 tool cases on macOS; there are **no blanket Mac skips**.
PowerShell shell history is replaced by `/bin/sh` history, NTFS junctions by directory symlinks, and the
Win32 `Global\` mutex fixture by a .NET named mutex with POSIX case-sensitive canonical
paths. The Win32 `\\?\` spelling is exercised only on Windows; the Unix serialization
case instead uses an equivalent `..` destination spelling. These Unix equivalents do
not claim coverage of Win32/NTFS mechanisms on a Mac. All shared behavior assertions
are retained; failures are not converted to skips.

Focused helper checks are separate from the full tool suite:

```bash
"$terminal/.venv/bin/python" -m unittest discover \
-s "$terminal" -p 'test_*.py' -v
```

They cover streaming UTF-8, terminal replies, isolation, named-mutex ownership, shell
quoting, controlling-terminal/session setup, real resize/SIGWINCH, Ctrl-C, exit/drain
behavior and owned-process/descriptor cleanup. POSIX PTY integration checks are
explicitly skipped **on Windows only**; this is not a skip of the real Mac picker suite.
The same PTY backend can be checked on Linux, but macOS and its .NET console behavior
must still be verified by running the full suite on a real Mac.

## Provenance

Adapted from [kartheekp-ms/dotnet-package-skills `tests/terminal`](https://github.com/kartheekp-ms/dotnet-package-skills/tree/59d3bc0d4fc80188d33bcc257d2a83c99a4cbfa3/tests/terminal)
at commit `59d3bc0d4fc80188d33bcc257d2a83c99a4cbfa3`. The five original files were
imported byte-for-byte before adapting the shared Python driver. The emulator and
pinned Python/Node manifests are unchanged. The upstream README declares MIT;
see this repository's [MIT license](../../../LICENSE). Dependencies retain their own
licenses (`@xterm/headless` is MIT).
49 changes: 49 additions & 0 deletions dotnet-package-skills/tests/terminal/emulator.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const { Terminal } = require('@xterm/headless');
const readline = require('node:readline');

let terminal;

function text(buffer) {
const rows = [];
for (let index = 0; index < buffer.length; index++) {
rows.push(buffer.getLine(index).translateToString(true));
}
return rows.join('\n').trimEnd();
}

async function main() {
for await (const line of readline.createInterface({ input: process.stdin })) {
const command = JSON.parse(line);
switch (command.action) {
case 'open':
terminal = new Terminal({
cols: command.columns,
rows: command.rows,
scrollback: 2000,
allowProposedApi: true
});
break;
case 'write':
await new Promise(resolve => terminal.write(command.data, resolve));
break;
case 'resize':
terminal.resize(command.columns, command.rows);
break;
case 'snapshot':
break;
default:
throw new Error(`Unknown emulator action: ${command.action}`);
}
console.log(JSON.stringify({
alternate: terminal.buffer.active.type === 'alternate',
normal: text(terminal.buffer.normal),
active: text(terminal.buffer.active)
}));
}
terminal?.dispose();
}

main().catch(error => {
console.error(error);
process.exitCode = 1;
});
22 changes: 22 additions & 0 deletions dotnet-package-skills/tests/terminal/mutex_holder.cs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Threading;

using var mutex = new Mutex(initiallyOwned: false, args[0]);
if (!mutex.WaitOne(TimeSpan.FromSeconds(15)))
{
throw new TimeoutException("The terminal fixture could not acquire the destination mutex.");
}

try
{
Console.WriteLine("locked");
Console.Out.Flush();
if (Console.ReadLine() is null)
{
throw new InvalidOperationException("The terminal fixture's release pipe closed unexpectedly.");
}
}
finally
{
mutex.ReleaseMutex();
}
19 changes: 19 additions & 0 deletions dotnet-package-skills/tests/terminal/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions dotnet-package-skills/tests/terminal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"private": true,
"description": "Terminal-emulator regression support for the .NET skill picker",
"devDependencies": {
"@xterm/headless": "5.5.0"
}
}
3 changes: 3 additions & 0 deletions dotnet-package-skills/tests/terminal/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pyte==0.8.2
pywinpty==3.0.5; sys_platform == "win32"
wcwidth==0.8.3
Loading