Skip to content

Commit e8ba1a7

Browse files
committed
Finalize v1.2.0 release artifact contract
1 parent 1d7e209 commit e8ba1a7

9 files changed

Lines changed: 760 additions & 639 deletions

File tree

.github/workflows/release.yml

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,17 @@ jobs:
187187
Set-Content -LiteralPath $sharedUninstall -Value $uninstallContent -Encoding Ascii
188188
Copy-Item "LICENSE" -Destination $sharedLicense -Force
189189
190-
$singleStage = "artifacts/release/package-staging/singlefile"
191-
$readyToRunStage = "artifacts/release/package-staging/readytorun"
192-
New-Item -ItemType Directory -Force -Path $singleStage | Out-Null
193-
New-Item -ItemType Directory -Force -Path $readyToRunStage | Out-Null
190+
$portableStage = "artifacts/release/package-staging/portable"
191+
New-Item -ItemType Directory -Force -Path $portableStage | Out-Null
194192
195-
Copy-Item "artifacts/release/singlefile/*" -Destination $singleStage -Recurse -Force
196-
Copy-Item "artifacts/release/readytorun/*" -Destination $readyToRunStage -Recurse -Force
193+
Copy-Item "artifacts/release/singlefile/*" -Destination $portableStage -Recurse -Force
197194
198-
Copy-Item $sharedUninstall -Destination (Join-Path $singleStage "uninstall.bat") -Force
199-
Copy-Item $sharedLicense -Destination (Join-Path $singleStage "LICENSE.md") -Force
200-
Copy-Item $sharedUninstall -Destination (Join-Path $readyToRunStage "uninstall.bat") -Force
201-
Copy-Item $sharedLicense -Destination (Join-Path $readyToRunStage "LICENSE.md") -Force
195+
Copy-Item $sharedUninstall -Destination (Join-Path $portableStage "uninstall.bat") -Force
196+
Copy-Item $sharedLicense -Destination (Join-Path $portableStage "LICENSE.md") -Force
202197
203-
$singleFileZip = "artifacts/release/packages/ThreadPilot_$version`_singlefile_win-x64.zip"
204-
$readyToRunZip = "artifacts/release/packages/ThreadPilot_$version`_readytorun_win-x64.zip"
198+
$portableZip = "artifacts/release/packages/ThreadPilot_$version`_Portable.zip"
205199
206-
Compress-Archive -Path "$singleStage/*" -DestinationPath $singleFileZip -Force
207-
Compress-Archive -Path "$readyToRunStage/*" -DestinationPath $readyToRunZip -Force
200+
Compress-Archive -Path "$portableStage/*" -DestinationPath $portableZip -Force
208201
209202
- name: Generate checksums
210203
shell: pwsh
@@ -390,11 +383,10 @@ jobs:
390383
mkdir -p public-release-assets
391384
392385
cp "release-assets/installer/ThreadPilot_${tag}_Setup.exe" "public-release-assets/ThreadPilot_${tag}_Setup.exe"
393-
cp "release-assets/packages/ThreadPilot_${tag}_singlefile_win-x64.zip" "public-release-assets/ThreadPilot_${tag}_singlefile_win-x64.zip"
394-
cp "release-assets/packages/ThreadPilot_${tag}_readytorun_win-x64.zip" "public-release-assets/ThreadPilot_${tag}_readytorun_win-x64.zip"
386+
cp "release-assets/packages/ThreadPilot_${tag}_Portable.zip" "public-release-assets/ThreadPilot_${tag}_Portable.zip"
395387
cp "release-assets/SHA256SUMS.txt" "public-release-assets/SHA256SUMS.txt"
396388
397-
expected_count=4
389+
expected_count=3
398390
actual_count="$(find public-release-assets -maxdepth 1 -type f | wc -l | tr -d ' ')"
399391
if [ "$actual_count" != "$expected_count" ]; then
400392
echo "Expected $expected_count public release asset files, found $actual_count."
@@ -437,8 +429,7 @@ jobs:
437429
body: ${{ steps.git-cliff.outputs.content }}
438430
files: |
439431
public-release-assets/ThreadPilot_${{ needs.build.outputs.tag }}_Setup.exe
440-
public-release-assets/ThreadPilot_${{ needs.build.outputs.tag }}_singlefile_win-x64.zip
441-
public-release-assets/ThreadPilot_${{ needs.build.outputs.tag }}_readytorun_win-x64.zip
432+
public-release-assets/ThreadPilot_${{ needs.build.outputs.tag }}_Portable.zip
442433
public-release-assets/SHA256SUMS.txt
443434
generate_release_notes: true
444435

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ winget search ThreadPilot
7171
| Package | Recommended use |
7272
|---|---|
7373
| `ThreadPilot_v<version>_Setup.exe` | Standard Windows installer for most users |
74-
| `ThreadPilot_v<version>_singlefile_win-x64.zip` | Portable/no-install deployment |
74+
| `ThreadPilot_v<version>_Portable.zip` | Portable/no-install deployment |
7575

7676
Optional checksum verification:
7777

7878
```powershell
7979
Get-FileHash .\ThreadPilot_v<version>_Setup.exe -Algorithm SHA256
80-
Get-FileHash .\ThreadPilot_v<version>_singlefile_win-x64.zip -Algorithm SHA256
80+
Get-FileHash .\ThreadPilot_v<version>_Portable.zip -Algorithm SHA256
8181
```
8282

8383
Compare the result with `SHA256SUMS.txt` from the same release.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
namespace ThreadPilot.Core.Tests
2+
{
3+
using System.Reflection;
4+
using System.Text.RegularExpressions;
5+
using System.Xml.Linq;
6+
using Moq;
7+
using ThreadPilot.Services;
8+
using ThreadPilot.ViewModels;
9+
10+
public sealed class MasksViewModelTests
11+
{
12+
[Fact]
13+
public void MasksView_SubtitleClarifiesPerProcessUse()
14+
{
15+
var document = LoadMasksViewXaml();
16+
var serialized = document.ToString(SaveOptions.DisableFormatting);
17+
18+
Assert.Contains("per-process use", serialized, StringComparison.Ordinal);
19+
}
20+
21+
[Fact]
22+
public void MasksView_ContainsEditingOnlyClarification()
23+
{
24+
var document = LoadMasksViewXaml();
25+
var serialized = document.ToString(SaveOptions.DisableFormatting);
26+
27+
Assert.Contains("does not change CPU affinity", serialized, StringComparison.Ordinal);
28+
Assert.Contains("until you apply it to a process", serialized, StringComparison.Ordinal);
29+
}
30+
31+
[Fact]
32+
public void MasksView_DefaultPresetTooltipWarnsNoAutoApply()
33+
{
34+
var document = LoadMasksViewXaml();
35+
var serialized = document.ToString(SaveOptions.DisableFormatting);
36+
37+
Assert.Contains("does not apply CPU affinity automatically", serialized, StringComparison.Ordinal);
38+
Assert.Contains("Pre-selected when ThreadPilot", serialized, StringComparison.Ordinal);
39+
}
40+
41+
[Fact]
42+
public void MasksView_NoGlobalAffinityControls()
43+
{
44+
var document = LoadMasksViewXaml();
45+
var serialized = document.ToString(SaveOptions.DisableFormatting);
46+
47+
Assert.DoesNotContain("apply globally", serialized, StringComparison.OrdinalIgnoreCase);
48+
Assert.DoesNotContain("global affinity", serialized, StringComparison.OrdinalIgnoreCase);
49+
Assert.DoesNotContain("disable SMT", serialized, StringComparison.OrdinalIgnoreCase);
50+
Assert.DoesNotContain("HyperThreading", serialized, StringComparison.OrdinalIgnoreCase);
51+
}
52+
53+
[Fact]
54+
public void MasksView_ToggleCpuTextClarifiesNoRunningProcessImpact()
55+
{
56+
var document = LoadMasksViewXaml();
57+
var serialized = document.ToString(SaveOptions.DisableFormatting);
58+
59+
Assert.Contains("do not affect running processes", serialized, StringComparison.Ordinal);
60+
}
61+
62+
[Fact]
63+
public void MasksView_DeleteWarningRefersToProcessesAndRules_NotGlobal()
64+
{
65+
var document = LoadMasksViewXaml();
66+
var serialized = document.ToString(SaveOptions.DisableFormatting);
67+
68+
Assert.DoesNotContain("all processes", serialized, StringComparison.OrdinalIgnoreCase);
69+
Assert.DoesNotContain("system-wide", serialized, StringComparison.OrdinalIgnoreCase);
70+
Assert.DoesNotContain("globally", serialized, StringComparison.OrdinalIgnoreCase);
71+
}
72+
73+
[Fact]
74+
public void MasksViewModel_ExposesOnlyCrudCommands()
75+
{
76+
var commandNames = typeof(MasksViewModel)
77+
.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
78+
.Where(p => p.PropertyType.Name.Contains("ommand", StringComparison.OrdinalIgnoreCase))
79+
.Select(p => p.Name)
80+
.ToList();
81+
82+
Assert.Contains("CreateMaskCommand", commandNames);
83+
Assert.Contains("DeleteMaskCommand", commandNames);
84+
Assert.Contains("DuplicateMaskCommand", commandNames);
85+
}
86+
87+
[Fact]
88+
public void MasksViewModel_HasNoAffinityApplyDependencies()
89+
{
90+
var constructorDependencies = typeof(MasksViewModel)
91+
.GetConstructors()
92+
.SelectMany(c => c.GetParameters())
93+
.Select(p => p.ParameterType.FullName ?? p.ParameterType.Name)
94+
.ToList();
95+
96+
Assert.DoesNotContain("AffinityApplyService", constructorDependencies, StringComparer.OrdinalIgnoreCase);
97+
Assert.DoesNotContain("ProcessAffinityApplyCoordinator", constructorDependencies, StringComparer.OrdinalIgnoreCase);
98+
Assert.DoesNotContain("IProcessService", constructorDependencies, StringComparer.OrdinalIgnoreCase);
99+
}
100+
101+
[Fact]
102+
public void MasksViewModel_HasNoAffinityApplyMethods()
103+
{
104+
var methods = typeof(MasksViewModel)
105+
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
106+
.Select(m => m.Name)
107+
.ToList();
108+
109+
Assert.DoesNotContain(methods, m => Regex.IsMatch(m, "Apply.*Affinity", RegexOptions.IgnoreCase));
110+
Assert.DoesNotContain(methods, m => Regex.IsMatch(m, "Set.*Affinity", RegexOptions.IgnoreCase));
111+
Assert.DoesNotContain(methods, m => Regex.IsMatch(m, "Apply.*Cpu.*Selection", RegexOptions.IgnoreCase));
112+
}
113+
114+
[Fact]
115+
public void MasksView_AllCoresProtectedDefaultInText()
116+
{
117+
var document = LoadMasksViewXaml();
118+
var serialized = document.ToString(SaveOptions.DisableFormatting);
119+
120+
Assert.Contains("All Cores is the protected default preset", serialized, StringComparison.Ordinal);
121+
}
122+
123+
private static XDocument LoadMasksViewXaml()
124+
{
125+
var repoRoot = GetRepositoryRoot();
126+
var path = Path.Combine(repoRoot, "Views", "MasksView.xaml");
127+
return XDocument.Load(path, LoadOptions.PreserveWhitespace);
128+
}
129+
130+
private static string GetRepositoryRoot()
131+
{
132+
var currentDir = AppContext.BaseDirectory;
133+
var dir = new DirectoryInfo(currentDir);
134+
while (dir != null && !File.Exists(Path.Combine(dir.FullName, "ThreadPilot_1.sln")))
135+
{
136+
dir = dir.Parent;
137+
}
138+
139+
if (dir == null)
140+
{
141+
throw new InvalidOperationException("Could not find repository root from " + currentDir);
142+
}
143+
144+
return dir.FullName;
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)