Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

### Fixed

- Fixed simulator test JSONL accuracy by keeping preflight discovery observational, preserving only user-supplied test selectors, discovering multiline parameterized Swift Testing tests, and parsing destination-suffixed xcodebuild test result lines.
- Removed stale physical-device log session status and shutdown cleanup for deprecated standalone device log capture, and corrected the device build-and-run tool description.
- Fixed mixed Swift Testing and XCTest summaries so simulator test text output no longer overcounts parameterized Swift Testing results or issue lines.
- Fixed CLI test summaries showing false-positive compiler errors from xcodebuild NSError dump lines, and added compiler-error snapshot coverage for simulator, device, and macOS build-style flows ([#383](https://github.com/getsentry/XcodeBuildMCP/issues/383)).
- Fixed simulator OSLog helper cleanup so server and daemon startup reconcile same-workspace orphaned log streams without stopping helpers owned by live sessions in other workspaces ([#382](https://github.com/getsentry/XcodeBuildMCP/issues/382)).
- Fixed Weather example test discovery and made CLI test progress visible while tests are running instead of leaving the last build phase displayed.

## [2.5.0-beta.1]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8B9291402FA3FCC300B2E371"
BuildableName = "Weather.app"
BlueprintName = "Weather"
ReferencedContainer = "container:Weather.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8B92914D2FA3FCC400B2E371"
BuildableName = "WeatherTests.xctest"
BlueprintName = "WeatherTests"
ReferencedContainer = "container:Weather.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8B9291572FA3FCC400B2E371"
BuildableName = "WeatherUITests.xctest"
BlueprintName = "WeatherUITests"
ReferencedContainer = "container:Weather.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</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 = "8B9291402FA3FCC300B2E371"
BuildableName = "Weather.app"
BlueprintName = "Weather"
ReferencedContainer = "container:Weather.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8B9291402FA3FCC300B2E371"
BuildableName = "Weather.app"
BlueprintName = "Weather"
ReferencedContainer = "container:Weather.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
94 changes: 94 additions & 0 deletions src/utils/__tests__/simulator-test-execution.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { describe, expect, it } from 'vitest';
import { createSimulatorTwoPhaseExecutionPlan } from '../simulator-test-execution.ts';
import type { TestPreflightResult } from '../test-preflight.ts';

function createPreflight(): TestPreflightResult {
return {
scheme: 'CalculatorApp',
configuration: 'Debug',
projectPath: '/tmp/CalculatorApp.xcodeproj',
destinationName: 'iPhone 17 Pro',
selectors: { onlyTesting: [], skipTesting: [] },
warnings: [],
completeness: 'complete',
totalTests: 2,
targets: [
{
name: 'CalculatorAppTests',
warnings: [],
files: [
{
path: '/tmp/CalculatorAppTests.swift',
tests: [
{
framework: 'xctest',
targetName: 'CalculatorAppTests',
typeName: 'CalculatorAppTests',
methodName: 'testAddition',
displayName: 'CalculatorAppTests/CalculatorAppTests/testAddition',
line: 10,
parameterized: false,
},
{
framework: 'swift-testing',
targetName: 'CalculatorAppTests',
typeName: 'ExpressionSuite',
methodName: 'evaluatesExpression',
displayName: 'CalculatorAppTests/ExpressionSuite/evaluatesExpression',
line: 20,
parameterized: true,
},
],
},
],
},
],
};
}

describe('createSimulatorTwoPhaseExecutionPlan', () => {
it('keeps preflight discovery observational instead of synthesizing only-testing selectors', () => {
const plan = createSimulatorTwoPhaseExecutionPlan({
extraArgs: ['-parallel-testing-enabled', 'YES'],
preflight: createPreflight(),
resultBundlePath: '/tmp/Calculator.xcresult',
});

expect(plan.buildArgs).toEqual(['-parallel-testing-enabled', 'YES']);
expect(plan.testArgs).toEqual([
'-parallel-testing-enabled',
'YES',
'-resultBundlePath',
'/tmp/Calculator.xcresult',
]);
expect(plan.usesExactSelectors).toBe(false);
});

it('preserves user-supplied selector arguments in both simulator test phases', () => {
const plan = createSimulatorTwoPhaseExecutionPlan({
extraArgs: [
'-only-testing:CalculatorAppTests/CalculatorAppTests/testAddition',
'-skip-testing',
'CalculatorAppTests/ExpressionSuite/evaluatesExpression',
],
preflight: createPreflight(),
});

expect(plan.buildArgs).toEqual([
'-only-testing:CalculatorAppTests/CalculatorAppTests/testAddition',
'-skip-testing',
'CalculatorAppTests/ExpressionSuite/evaluatesExpression',
]);
expect(plan.testArgs).toEqual(plan.buildArgs);
expect(plan.usesExactSelectors).toBe(true);
});

it('keeps resultBundlePath out of build-for-testing args and includes it for test-without-building', () => {
const plan = createSimulatorTwoPhaseExecutionPlan({
extraArgs: ['-resultBundlePath', '/tmp/UserProvided.xcresult'],
});

expect(plan.buildArgs).toEqual([]);
expect(plan.testArgs).toEqual(['-resultBundlePath', '/tmp/UserProvided.xcresult']);
});
});
54 changes: 54 additions & 0 deletions src/utils/__tests__/swift-test-discovery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest';
import { createMockFileSystemExecutor } from '../../test-utils/mock-executors.ts';
import { discoverSwiftTestsInFiles } from '../swift-test-discovery.ts';

describe('discoverSwiftTestsInFiles', () => {
it('discovers Swift Testing functions with multiline parameterized Test attributes', async () => {
const filePath = '/tmp/CalculatorServiceTests.swift';
const fileSystemExecutor = createMockFileSystemExecutor({
readFile: async () => `
import Testing

struct CalculatorServiceTests {
@Test(
"evaluates decimal operations",
arguments: [
("1 + 1", "2"),
("4 / 2", "2"),
]
)
func evaluatesDecimalOperations(expression: String, expected: String) async throws {}

@Test(arguments: ["+", "-", "×"])
func evaluatesOperators(symbol: String) async throws {}
}
`,
});

const files = await discoverSwiftTestsInFiles(
'CalculatorAppFeatureTests',
[filePath],
fileSystemExecutor,
);

expect(files).toHaveLength(1);
expect(files[0].tests).toMatchObject([
{
framework: 'swift-testing',
targetName: 'CalculatorAppFeatureTests',
typeName: 'CalculatorServiceTests',
methodName: 'evaluatesDecimalOperations',
displayName: 'CalculatorAppFeatureTests/CalculatorServiceTests/evaluatesDecimalOperations',
parameterized: true,
},
{
framework: 'swift-testing',
targetName: 'CalculatorAppFeatureTests',
typeName: 'CalculatorServiceTests',
methodName: 'evaluatesOperators',
displayName: 'CalculatorAppFeatureTests/CalculatorServiceTests/evaluatesOperators',
parameterized: true,
},
]);
});
});
39 changes: 0 additions & 39 deletions src/utils/__tests__/swift-testing-line-parsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
parseSwiftTestingIssueLine,
parseSwiftTestingRunSummary,
parseSwiftTestingContinuationLine,
parseXcodebuildSwiftTestingLine,
} from '../swift-testing-line-parsers.ts';

describe('Swift Testing line parsers', () => {
Expand Down Expand Up @@ -259,42 +258,4 @@ describe('Swift Testing line parsers', () => {
expect(parseSwiftTestingContinuationLine('regular line')).toBeNull();
});
});

describe('parseXcodebuildSwiftTestingLine', () => {
it('should parse a passed test case', () => {
const result = parseXcodebuildSwiftTestingLine(
"Test case 'MCPTestTests/appNameIsCorrect()' passed on 'My Mac - MCPTest (78757)' (0.000 seconds)",
);
expect(result).toEqual({
status: 'passed',
rawName: 'MCPTestTests/appNameIsCorrect()',
suiteName: 'MCPTestTests',
testName: 'appNameIsCorrect()',
durationText: '0.000s',
});
});

it('should parse a failed test case', () => {
const result = parseXcodebuildSwiftTestingLine(
"Test case 'MCPTestTests/deliberateFailure()' failed on 'My Mac - MCPTest (78757)' (0.000 seconds)",
);
expect(result).toEqual({
status: 'failed',
rawName: 'MCPTestTests/deliberateFailure()',
suiteName: 'MCPTestTests',
testName: 'deliberateFailure()',
durationText: '0.000s',
});
});

it('should return null for XCTest format lines', () => {
expect(
parseXcodebuildSwiftTestingLine("Test Case '-[Suite test]' passed (0.001 seconds)."),
).toBeNull();
});

it('should return null for non-matching lines', () => {
expect(parseXcodebuildSwiftTestingLine('random text')).toBeNull();
});
});
});
Loading
Loading