Increment 0: add XCTest target, testing process & 10x value plan
- Add TopManagerTests unit-test target (hosted, @testable import) - 14 baseline tests for formatters and model math (all green) - scripts/test.sh build+test loop driver - docs/TESTING.md (5-gate process), docs/VALUE_PLAN.md (10x rubric) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
341C12CED58388D32ED6C5BB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F1ACD0492CAB741595DEAA /* ContentView.swift */; };
|
341C12CED58388D32ED6C5BB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F1ACD0492CAB741595DEAA /* ContentView.swift */; };
|
||||||
35852165B2DA76AB7F073723 /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D71CC63FC2FEF52A069372E /* MenuBarView.swift */; };
|
35852165B2DA76AB7F073723 /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D71CC63FC2FEF52A069372E /* MenuBarView.swift */; };
|
||||||
4427B8406B25B8FEDF27CDBA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 334573CEB47D60570A40B55D /* Assets.xcassets */; };
|
4427B8406B25B8FEDF27CDBA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 334573CEB47D60570A40B55D /* Assets.xcassets */; };
|
||||||
|
49D85FF611927294159C89D1 /* FormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 301352EB5B362C732CE1A246 /* FormattingTests.swift */; };
|
||||||
501C13D57386C179C0B3B14B /* PowerStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6038CCDF3BDF0F8BC93C9D4 /* PowerStorageView.swift */; };
|
501C13D57386C179C0B3B14B /* PowerStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6038CCDF3BDF0F8BC93C9D4 /* PowerStorageView.swift */; };
|
||||||
744F52D036D1F3E081923382 /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF69D4888FDBB3669619A1A /* ProcessInfo.swift */; };
|
744F52D036D1F3E081923382 /* ProcessInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF69D4888FDBB3669619A1A /* ProcessInfo.swift */; };
|
||||||
7A1144E43159D96564C13164 /* MemoryInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3EDCDEDF4A0CE1921A3B25 /* MemoryInfo.swift */; };
|
7A1144E43159D96564C13164 /* MemoryInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3EDCDEDF4A0CE1921A3B25 /* MemoryInfo.swift */; };
|
||||||
@@ -38,7 +39,18 @@
|
|||||||
FA252F9182ADDA26AD1E54DE /* CPUChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D5A13CC5B9CF1B90CC2214 /* CPUChartView.swift */; };
|
FA252F9182ADDA26AD1E54DE /* CPUChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D5A13CC5B9CF1B90CC2214 /* CPUChartView.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
B01CE3A16D70795D608951EE /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 4BAFF5CBBA9A1302C6CCDCD2 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = F86DD5615146D759EB0D44EB;
|
||||||
|
remoteInfo = TopManager;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0F5A69E0EE7FDD44157911D7 /* TopManagerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TopManagerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
110A314A63AE355A9E04990D /* ProcessDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessDetailView.swift; sourceTree = "<group>"; };
|
110A314A63AE355A9E04990D /* ProcessDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessDetailView.swift; sourceTree = "<group>"; };
|
||||||
162FC58F6C44EC1DD7022A86 /* DiskMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskMonitor.swift; sourceTree = "<group>"; };
|
162FC58F6C44EC1DD7022A86 /* DiskMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskMonitor.swift; sourceTree = "<group>"; };
|
||||||
19ED582C412C63A0A2E2103E /* CPUMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUMonitor.swift; sourceTree = "<group>"; };
|
19ED582C412C63A0A2E2103E /* CPUMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUMonitor.swift; sourceTree = "<group>"; };
|
||||||
@@ -46,6 +58,7 @@
|
|||||||
27FDD19B8DBB7D955A5ECB5B /* MemoryMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryMonitor.swift; sourceTree = "<group>"; };
|
27FDD19B8DBB7D955A5ECB5B /* MemoryMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryMonitor.swift; sourceTree = "<group>"; };
|
||||||
295232B4C746511757DCF8FE /* ProcessMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessMonitor.swift; sourceTree = "<group>"; };
|
295232B4C746511757DCF8FE /* ProcessMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessMonitor.swift; sourceTree = "<group>"; };
|
||||||
3010F605712E09175D9D8878 /* NetworkInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInfo.swift; sourceTree = "<group>"; };
|
3010F605712E09175D9D8878 /* NetworkInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInfo.swift; sourceTree = "<group>"; };
|
||||||
|
301352EB5B362C732CE1A246 /* FormattingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingTests.swift; sourceTree = "<group>"; };
|
||||||
334573CEB47D60570A40B55D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
334573CEB47D60570A40B55D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
33BCE156CB39EDADFFC938F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
33BCE156CB39EDADFFC938F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
347E47F825E496F40FD8ED11 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
|
347E47F825E496F40FD8ED11 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
|
||||||
@@ -92,6 +105,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
36BB5A20B1330E54F2DCF357 /* TopManager */,
|
36BB5A20B1330E54F2DCF357 /* TopManager */,
|
||||||
|
D078BA97D4F498E5282AC5E0 /* TopManagerTests */,
|
||||||
E6438D9AF8EDEFEDCB087B38 /* Products */,
|
E6438D9AF8EDEFEDCB087B38 /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -183,10 +197,19 @@
|
|||||||
path = PowerStorage;
|
path = PowerStorage;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D078BA97D4F498E5282AC5E0 /* TopManagerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
301352EB5B362C732CE1A246 /* FormattingTests.swift */,
|
||||||
|
);
|
||||||
|
path = TopManagerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E6438D9AF8EDEFEDCB087B38 /* Products */ = {
|
E6438D9AF8EDEFEDCB087B38 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A3A3D0B1EB522C9975F71DA8 /* TopManager.app */,
|
A3A3D0B1EB522C9975F71DA8 /* TopManager.app */,
|
||||||
|
0F5A69E0EE7FDD44157911D7 /* TopManagerTests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -203,6 +226,24 @@
|
|||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
D28E798AA7BEAA0638DFD52E /* TopManagerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 9D898AF16ACAC9453397ADCE /* Build configuration list for PBXNativeTarget "TopManagerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
692062A41592E988A6547E4C /* Sources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
48A04DA713F139236C91459E /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = TopManagerTests;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = TopManagerTests;
|
||||||
|
productReference = 0F5A69E0EE7FDD44157911D7 /* TopManagerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
F86DD5615146D759EB0D44EB /* TopManager */ = {
|
F86DD5615146D759EB0D44EB /* TopManager */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 56829675F6738AEDCFCDEE52 /* Build configuration list for PBXNativeTarget "TopManager" */;
|
buildConfigurationList = 56829675F6738AEDCFCDEE52 /* Build configuration list for PBXNativeTarget "TopManager" */;
|
||||||
@@ -230,6 +271,10 @@
|
|||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastUpgradeCheck = 1500;
|
LastUpgradeCheck = 1500;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
D28E798AA7BEAA0638DFD52E = {
|
||||||
|
DevelopmentTeam = P3663392R2;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
};
|
||||||
F86DD5615146D759EB0D44EB = {
|
F86DD5615146D759EB0D44EB = {
|
||||||
DevelopmentTeam = P3663392R2;
|
DevelopmentTeam = P3663392R2;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
@@ -251,6 +296,7 @@
|
|||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
F86DD5615146D759EB0D44EB /* TopManager */,
|
F86DD5615146D759EB0D44EB /* TopManager */,
|
||||||
|
D28E798AA7BEAA0638DFD52E /* TopManagerTests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -268,6 +314,14 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
692062A41592E988A6547E4C /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
49D85FF611927294159C89D1 /* FormattingTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
CF5286324D09B9417D3070C3 /* Sources */ = {
|
CF5286324D09B9417D3070C3 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -304,6 +358,14 @@
|
|||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
48A04DA713F139236C91459E /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = F86DD5615146D759EB0D44EB /* TopManager */;
|
||||||
|
targetProxy = B01CE3A16D70795D608951EE /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
00F8AAE6BB8D4ED66312B6CB /* Release */ = {
|
00F8AAE6BB8D4ED66312B6CB /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
@@ -390,6 +452,24 @@
|
|||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
C0DE23B98ECF14373EAC79A6 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@loader_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.topmanager.TopManagerTests;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TopManager.app/Contents/MacOS/TopManager";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
C4D5AD1B7274BBED9D423AC0 /* Debug */ = {
|
C4D5AD1B7274BBED9D423AC0 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -468,6 +548,24 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
EABC414CD1181D6B70269064 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@loader_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.topmanager.TopManagerTests;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TopManager.app/Contents/MacOS/TopManager";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
@@ -480,6 +578,15 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Debug;
|
defaultConfigurationName = Debug;
|
||||||
};
|
};
|
||||||
|
9D898AF16ACAC9453397ADCE /* Build configuration list for PBXNativeTarget "TopManagerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
EABC414CD1181D6B70269064 /* Debug */,
|
||||||
|
C0DE23B98ECF14373EAC79A6 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Debug;
|
||||||
|
};
|
||||||
A70528C1B1F0BAB178B0B048 /* Build configuration list for PBXProject "TopManager" */ = {
|
A70528C1B1F0BAB178B0B048 /* Build configuration list for PBXProject "TopManager" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|||||||
118
TopManager.xcodeproj/xcshareddata/xcschemes/TopManager.xcscheme
Normal file
118
TopManager.xcodeproj/xcshareddata/xcschemes/TopManager.xcscheme
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1500"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
runPostActionsOnFailure = "NO">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F86DD5615146D759EB0D44EB"
|
||||||
|
BuildableName = "TopManager.app"
|
||||||
|
BlueprintName = "TopManager"
|
||||||
|
ReferencedContainer = "container:TopManager.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "NO"
|
||||||
|
buildForProfiling = "NO"
|
||||||
|
buildForArchiving = "NO"
|
||||||
|
buildForAnalyzing = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D28E798AA7BEAA0638DFD52E"
|
||||||
|
BuildableName = "TopManagerTests.xctest"
|
||||||
|
BlueprintName = "TopManagerTests"
|
||||||
|
ReferencedContainer = "container:TopManager.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
onlyGenerateCoverageForSpecifiedTargets = "NO">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F86DD5615146D759EB0D44EB"
|
||||||
|
BuildableName = "TopManager.app"
|
||||||
|
BlueprintName = "TopManager"
|
||||||
|
ReferencedContainer = "container:TopManager.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D28E798AA7BEAA0638DFD52E"
|
||||||
|
BuildableName = "TopManagerTests.xctest"
|
||||||
|
BlueprintName = "TopManagerTests"
|
||||||
|
ReferencedContainer = "container:TopManager.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F86DD5615146D759EB0D44EB"
|
||||||
|
BuildableName = "TopManager.app"
|
||||||
|
BlueprintName = "TopManager"
|
||||||
|
ReferencedContainer = "container:TopManager.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F86DD5615146D759EB0D44EB"
|
||||||
|
BuildableName = "TopManager.app"
|
||||||
|
BlueprintName = "TopManager"
|
||||||
|
ReferencedContainer = "container:TopManager.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
114
TopManagerTests/FormattingTests.swift
Normal file
114
TopManagerTests/FormattingTests.swift
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import TopManager
|
||||||
|
|
||||||
|
/// Baseline tests proving the test harness works and locking in the existing
|
||||||
|
/// formatting / model-math behavior so later refactors can't silently break it.
|
||||||
|
final class FormattingTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: - Byte formatting
|
||||||
|
|
||||||
|
func testFormatBytesZero() {
|
||||||
|
// ByteCountFormatter renders 0 as "Zero KB"/"Zero bytes" depending on OS/locale.
|
||||||
|
let s = formatBytes(UInt64(0)).lowercased()
|
||||||
|
XCTAssertTrue(s.contains("zero") || s.contains("0"), "Unexpected zero rendering: \(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatBytesGigabyteScale() {
|
||||||
|
// 2 GiB in binary style
|
||||||
|
let twoGiB = UInt64(2) * 1024 * 1024 * 1024
|
||||||
|
let s = formatBytes(twoGiB)
|
||||||
|
XCTAssertTrue(s.contains("GB"), "Expected GB unit, got \(s)")
|
||||||
|
XCTAssertTrue(s.contains("2"), "Expected value 2, got \(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatBytesNegativeDoubleClampsToZero() {
|
||||||
|
// Double overload clamps negatives to 0 rather than trapping on UInt64(negative)
|
||||||
|
let s = formatBytes(Double(-500)).lowercased()
|
||||||
|
XCTAssertTrue(s.contains("zero") || s.contains("0"), "Unexpected clamp rendering: \(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatBytesPerSecondBelowOne() {
|
||||||
|
XCTAssertEqual(formatBytesPerSecond(0.4), "0 B/s")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatBytesPerSecondSuffix() {
|
||||||
|
let s = formatBytesPerSecond(5 * 1024 * 1024) // 5 MiB/s
|
||||||
|
XCTAssertTrue(s.hasSuffix("/s"), "Expected /s suffix, got \(s)")
|
||||||
|
XCTAssertTrue(s.contains("MB"), "Expected MB unit, got \(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Percentage
|
||||||
|
|
||||||
|
func testFormatPercentageDefaultDecimals() {
|
||||||
|
XCTAssertEqual(formatPercentage(12.345), "12.3%")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatPercentageCustomDecimals() {
|
||||||
|
XCTAssertEqual(formatPercentage(12.345, decimals: 2), "12.35%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Uptime
|
||||||
|
|
||||||
|
func testFormatUptimeMinutesOnly() {
|
||||||
|
XCTAssertEqual(formatUptime(45 * 60), "45m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatUptimeHoursAndMinutes() {
|
||||||
|
XCTAssertEqual(formatUptime(3 * 3600 + 12 * 60), "3h 12m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatUptimeDays() {
|
||||||
|
XCTAssertEqual(formatUptime(2 * 86400 + 5 * 3600 + 9 * 60), "2d 5h 9m")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Model math
|
||||||
|
|
||||||
|
func testMemoryUsagePercentage() {
|
||||||
|
let mem = MemoryInfo(
|
||||||
|
totalMemory: 16_000_000_000,
|
||||||
|
usedMemory: 8_000_000_000,
|
||||||
|
freeMemory: 8_000_000_000,
|
||||||
|
activeMemory: 4_000_000_000,
|
||||||
|
inactiveMemory: 2_000_000_000,
|
||||||
|
wiredMemory: 2_000_000_000,
|
||||||
|
compressedMemory: 1_000_000_000,
|
||||||
|
cachedMemory: 1_000_000_000,
|
||||||
|
swapUsed: 0,
|
||||||
|
swapTotal: 0,
|
||||||
|
memoryPressure: .nominal
|
||||||
|
)
|
||||||
|
XCTAssertEqual(mem.usagePercentage, 50.0, accuracy: 0.001)
|
||||||
|
XCTAssertEqual(mem.appMemory, 6_000_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMemoryUsagePercentageZeroTotalIsSafe() {
|
||||||
|
let mem = MemoryInfo(
|
||||||
|
totalMemory: 0, usedMemory: 0, freeMemory: 0, activeMemory: 0,
|
||||||
|
inactiveMemory: 0, wiredMemory: 0, compressedMemory: 0, cachedMemory: 0,
|
||||||
|
swapUsed: 0, swapTotal: 0, memoryPressure: .unknown
|
||||||
|
)
|
||||||
|
XCTAssertEqual(mem.usagePercentage, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testVolumeUsagePercentageAndUsedSpace() {
|
||||||
|
let vol = VolumeInfo(
|
||||||
|
name: "Macintosh HD",
|
||||||
|
mountPoint: "/",
|
||||||
|
totalSpace: 1000,
|
||||||
|
freeSpace: 250,
|
||||||
|
fileSystem: "APFS",
|
||||||
|
isRemovable: false,
|
||||||
|
isInternal: true
|
||||||
|
)
|
||||||
|
XCTAssertEqual(vol.usedSpace, 750)
|
||||||
|
XCTAssertEqual(vol.usagePercentage, 75.0, accuracy: 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testVolumeFreeGreaterThanTotalClampsUsedToZero() {
|
||||||
|
let vol = VolumeInfo(
|
||||||
|
name: "weird", mountPoint: "/x", totalSpace: 100, freeSpace: 200,
|
||||||
|
fileSystem: "APFS", isRemovable: false, isInternal: true
|
||||||
|
)
|
||||||
|
XCTAssertEqual(vol.usedSpace, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
67
docs/TESTING.md
Normal file
67
docs/TESTING.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# TopManager — Testing Process
|
||||||
|
|
||||||
|
This document defines the repeatable test process used while evolving TopManager.
|
||||||
|
Every feature increment must pass all five gates below before it is considered done.
|
||||||
|
|
||||||
|
## Gate 1 — Compile
|
||||||
|
```bash
|
||||||
|
scripts/test.sh build
|
||||||
|
```
|
||||||
|
Fast feedback that the code compiles for `platform=macOS,arch=arm64`. First gate on every change.
|
||||||
|
|
||||||
|
## Gate 2 — Unit tests (automated, self-run)
|
||||||
|
```bash
|
||||||
|
scripts/test.sh test # or: xcodebuild ... test
|
||||||
|
```
|
||||||
|
Pure-logic is covered by XCTest in `TopManagerTests/` using `@testable import TopManager`.
|
||||||
|
The suite intentionally targets deterministic logic (no live system state):
|
||||||
|
|
||||||
|
- **Formatting / model math** — bytes, rates, percentages, uptime, usage %.
|
||||||
|
- **MetricsStore** — ring-buffer trim, persistence round-trip, downsampling per range.
|
||||||
|
- **Energy heuristic** — score is monotonic in CPU and wakeups, clamped to 0–100.
|
||||||
|
- **Alert engine** — threshold crossings fire/clear correctly, sustained-window logic,
|
||||||
|
de-duplication (no alert storms), severity ordering.
|
||||||
|
- **Health score** — 0–100, decreases with pressure/thermal/swap/disk load.
|
||||||
|
- **Battery math** — time-remaining and capacity-health formatting.
|
||||||
|
|
||||||
|
Tests must stay hermetic: no sleeping on real timers, no asserting on live CPU numbers.
|
||||||
|
|
||||||
|
## Gate 3 — Runtime smoke test (self-run on this machine)
|
||||||
|
Launch the built app, drive real load, confirm no crash and data populates.
|
||||||
|
```bash
|
||||||
|
# build a runnable .app
|
||||||
|
xcodebuild -project TopManager.xcodeproj -scheme TopManager -configuration Debug \
|
||||||
|
-destination 'platform=macOS,arch=arm64' build CODE_SIGNING_ALLOWED=NO
|
||||||
|
APP=$(find ~/Library/Developer/Xcode/DerivedData/TopManager-*/Build/Products/Debug -name TopManager.app | head -1)
|
||||||
|
open "$APP"
|
||||||
|
# generate CPU load in the background, then verify TopManager surfaces it
|
||||||
|
yes > /dev/null & LOAD=$!; sleep 8; kill $LOAD
|
||||||
|
```
|
||||||
|
Verify: process list shows the load generator near the top, charts move, no crash in
|
||||||
|
`Console`/stderr, menu bar updates, alerts fire when thresholds are crossed.
|
||||||
|
|
||||||
|
## Gate 4 — Ground-truth cross-checks
|
||||||
|
TopManager's numbers are validated against OS tools so we don't ship wrong data:
|
||||||
|
|
||||||
|
| TopManager metric | Ground truth command |
|
||||||
|
|------------------------|---------------------------------------------|
|
||||||
|
| Per-process CPU/mem | `ps -Ao pid,pcpu,pmem,comm -r | head` |
|
||||||
|
| System memory | `vm_stat`, `sysctl vm.swapusage` |
|
||||||
|
| Disk volumes / free | `df -h` |
|
||||||
|
| Battery / power | `pmset -g batt`, `ioreg -rn AppleSmartBattery` |
|
||||||
|
| Thermal state | `pmset -g therm` |
|
||||||
|
| Network throughput | `netstat -ib` |
|
||||||
|
|
||||||
|
Small deltas are expected (sampling windows differ); order-of-magnitude or ranking
|
||||||
|
mismatches are bugs.
|
||||||
|
|
||||||
|
## Gate 5 — Regression: original features intact
|
||||||
|
Manually confirm each shipped feature still works: Processes (sort/search/kill/suspend/
|
||||||
|
resume), Apps (activate/hide/quit/force-quit/copy bundle id), Performance (CPU/Mem/Net
|
||||||
|
charts), Power & Storage (system/GPU/storage/network), and the menu-bar readout.
|
||||||
|
|
||||||
|
## Loop
|
||||||
|
```
|
||||||
|
implement slice → Gate 1 → Gate 2 → Gate 3 → Gate 4 → Gate 5 → checkpoint commit
|
||||||
|
```
|
||||||
|
Repeat per increment until the value rubric in `docs/VALUE_PLAN.md` is met.
|
||||||
38
docs/VALUE_PLAN.md
Normal file
38
docs/VALUE_PLAN.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# TopManager — 10× Value Plan
|
||||||
|
|
||||||
|
## Thesis
|
||||||
|
TopManager today is a polished, **read-only Activity Monitor clone**: it *shows* numbers.
|
||||||
|
Its value ceiling is "I can see what's using my Mac and kill a process."
|
||||||
|
|
||||||
|
To make it ~10× more valuable we move it from **viewer → system-intelligence & control
|
||||||
|
center**: it watches the system for you, remembers its history, explains problems, exposes
|
||||||
|
data Activity Monitor hides, and lets you act — including always-on from the menu bar.
|
||||||
|
|
||||||
|
All existing features are preserved (several are improved).
|
||||||
|
|
||||||
|
## Value rubric (capability scoring)
|
||||||
|
| Dimension | Before | After (target) |
|
||||||
|
|---|---|---|
|
||||||
|
| Per-process depth | CPU, mem, threads, state | + disk I/O rate, energy impact, args, open files, full path, tree |
|
||||||
|
| Time memory | 60 in-RAM points, lost on quit | persistent history, minutes → 24h, survives restart |
|
||||||
|
| Proactivity | none | threshold alerts + native notifications + health score + diagnosis |
|
||||||
|
| Energy / battery | **absent** | battery health (cycles/condition/capacity) + per-process energy |
|
||||||
|
| Always-on surface | bare "12%" | rich menu-bar HUD: CPU/RAM/net + top consumers + quick-kill |
|
||||||
|
| Customization | none (all hardcoded) | Settings: refresh rate, menu metric, thresholds, launch-at-login |
|
||||||
|
|
||||||
|
## Increments
|
||||||
|
0. **Test harness** — XCTest target, `@testable import`, 5-gate process. ✅
|
||||||
|
1. **Persistent history** — `MetricsStore` ring buffer on disk; 1m/5m/1h/24h charts.
|
||||||
|
2. **Deep per-process data** — disk I/O, energy impact, args, open files, path; surface the
|
||||||
|
(currently unused) `ProcessDetailView` as a real inspector; new sortable columns.
|
||||||
|
3. **Battery + energy** — IOKit `PowerMonitor`; Energy/Battery UI; menu-bar battery.
|
||||||
|
4. **Alerts + health** — `AlertEngine`, `UserNotifications`, in-app inbox, 0–100 health score,
|
||||||
|
"why is my Mac slow?" diagnosis.
|
||||||
|
5. **Settings + rich menu bar** — configurable refresh/metric/thresholds, launch-at-login
|
||||||
|
(`SMAppService`), menu-bar HUD with top consumers + quick actions.
|
||||||
|
6. **Verification** — full test run, runtime smoke, ground-truth cross-checks, sign, commit.
|
||||||
|
|
||||||
|
## Non-negotiables
|
||||||
|
- Never break a shipped feature (regression Gate 5).
|
||||||
|
- No new entitlements / no root / no privileged helper — must run & sign as-is.
|
||||||
|
- Every increment passes all five gates in `docs/TESTING.md` before moving on.
|
||||||
31
project.yml
31
project.yml
@@ -33,3 +33,34 @@ targets:
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
||||||
info:
|
info:
|
||||||
path: TopManager/Info.plist
|
path: TopManager/Info.plist
|
||||||
|
|
||||||
|
TopManagerTests:
|
||||||
|
type: bundle.unit-test
|
||||||
|
platform: macOS
|
||||||
|
sources:
|
||||||
|
- path: TopManagerTests
|
||||||
|
excludes:
|
||||||
|
- "**/.DS_Store"
|
||||||
|
dependencies:
|
||||||
|
- target: TopManager
|
||||||
|
settings:
|
||||||
|
base:
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER: com.topmanager.TopManagerTests
|
||||||
|
GENERATE_INFOPLIST_FILE: YES
|
||||||
|
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/TopManager.app/Contents/MacOS/TopManager"
|
||||||
|
BUNDLE_LOADER: "$(TEST_HOST)"
|
||||||
|
CODE_SIGN_STYLE: Automatic
|
||||||
|
|
||||||
|
schemes:
|
||||||
|
TopManager:
|
||||||
|
build:
|
||||||
|
targets:
|
||||||
|
TopManager: all
|
||||||
|
TopManagerTests: [test]
|
||||||
|
run:
|
||||||
|
config: Debug
|
||||||
|
test:
|
||||||
|
config: Debug
|
||||||
|
targets:
|
||||||
|
- TopManagerTests
|
||||||
|
gatherCoverageData: false
|
||||||
|
|||||||
25
scripts/test.sh
Executable file
25
scripts/test.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Fast build+test loop driver for TopManager.
|
||||||
|
# Usage:
|
||||||
|
# scripts/test.sh build # compile only (fast gate)
|
||||||
|
# scripts/test.sh test # compile + run unit tests
|
||||||
|
# scripts/test.sh # same as "test"
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
ACTION="${1:-test}"
|
||||||
|
DEST='platform=macOS,arch=arm64'
|
||||||
|
COMMON=(-project TopManager.xcodeproj -scheme TopManager -configuration Debug -destination "$DEST" CODE_SIGNING_ALLOWED=NO)
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
build)
|
||||||
|
xcodebuild "${COMMON[@]}" build 2>&1 | tail -20
|
||||||
|
;;
|
||||||
|
test)
|
||||||
|
xcodebuild "${COMMON[@]}" test 2>&1 \
|
||||||
|
| grep -E "Test Suite|Executed|error:|warning:.*unused|BUILD|TEST (SUCCEEDED|FAILED)|failed \(" \
|
||||||
|
| tail -40
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown action: $ACTION" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user