Skip to content
Merged
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 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