|
| 1 | +import XCTest |
| 2 | +@testable import xcsift |
| 3 | + |
| 4 | +class BuildPhasesTest: XCTestCase { |
| 5 | + let parser = OutputParser() |
| 6 | + |
| 7 | + func testPhaseScriptExecutionFailureBasic() { |
| 8 | + let output = """ |
| 9 | + /bin/sh -c /Users/dhavalkansara/Library/Developer/Xcode/DerivedData/AFEiOS-gctxucyuhlhesnfkbuxfswkozboo/Build/Intermediates.noindex/AFEiOS.build/Debug-iphoneos/AFEiOS.build/Script-19DAA30A22C0FB0100A039E2.sh |
| 10 | + The path lib/main.dart does not exist |
| 11 | + The path does not exist |
| 12 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 13 | + """ |
| 14 | + |
| 15 | + let result = parser.parse(input: output) |
| 16 | + |
| 17 | + XCTAssertEqual(result.summary.errors, 1, "Should detect PhaseScriptExecution failure") |
| 18 | + XCTAssertFalse(result.errors.isEmpty, "Should have at least one error") |
| 19 | + |
| 20 | + let error = result.errors[0] |
| 21 | + XCTAssertNil(error.file, "PhaseScriptExecution error should not have file") |
| 22 | + XCTAssertNil(error.line, "PhaseScriptExecution error should not have line number") |
| 23 | + XCTAssertTrue( |
| 24 | + error.message.contains("Command PhaseScriptExecution failed"), |
| 25 | + "Error message should contain failure indicator" |
| 26 | + ) |
| 27 | + XCTAssertTrue( |
| 28 | + error.message.contains("The path lib/main.dart does not exist"), |
| 29 | + "Error message should include context from preceding lines" |
| 30 | + ) |
| 31 | + } |
| 32 | + |
| 33 | + func testPhaseScriptExecutionWithHermesFramework() { |
| 34 | + let output = """ |
| 35 | + Run script build phase '[CP-User] [Hermes] Replace Hermes for the right configuration, if needed' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. |
| 36 | + PhaseScriptExecution [CP-User]\\ [Hermes]\\ Replace\\ Hermes\\ for\\ the\\ right\\ configuration,\\ if\\ needed /Library/Developer/Xcode/DerivedData/myProjectName-gzdlehmipieiindfjyfrhhcjupam/Build/Intermediates.noindex/ArchiveIntermediates/myProjectName/IntermediateBuildFilesPath/Pods.build/Release-iphoneos/hermes-engine.build/Script-46EB2E0002C950.sh (in target 'hermes-engine' from project 'Pods') |
| 37 | + Node found at: /var/folders/d5/f1gffcfx27ngwvmw8v8jdm7m0000gn/T/yarn--1704767526546-0.12516067745295967/node |
| 38 | + /Library/Developer/Xcode/DerivedData/myProjectName-gzdlehmipieiindfjyfrhhcjupam/Build/Intermediates.noindex/ArchiveIntermediates/myProjectName/IntermediateBuildFilesPath/Pods.build/Release-iphoneos/hermes-engine.build/Script-46EB2E0002C950.sh: line 9: /var/folders/d5/f1gffcfx27ngwvmw8v8jdm7m0000gn/T/yarn--1704767526546-0.12516067745295967/node: No such file or directory |
| 39 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 40 | + """ |
| 41 | + |
| 42 | + let result = parser.parse(input: output) |
| 43 | + |
| 44 | + XCTAssertEqual(result.summary.errors, 1, "Should detect PhaseScriptExecution failure for Hermes") |
| 45 | + XCTAssertFalse(result.errors.isEmpty, "Should have at least one error") |
| 46 | + |
| 47 | + let error = result.errors[0] |
| 48 | + XCTAssertTrue( |
| 49 | + error.message.contains("Command PhaseScriptExecution failed"), |
| 50 | + "Error message should contain failure indicator" |
| 51 | + ) |
| 52 | + XCTAssertTrue( |
| 53 | + error.message.contains("No such file or directory"), |
| 54 | + "Error message should include context about missing file" |
| 55 | + ) |
| 56 | + } |
| 57 | + |
| 58 | + func testPhaseScriptExecutionWithUnityGameAssembly() { |
| 59 | + let output = """ |
| 60 | + /bin/sh -c /Users/evgeniyasenchurova/Library/Developer/Xcode/DerivedData/Unity-iPhone-gtnilxmbqexxvtcauewfdmpfbvfe/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/IntermediateBuildFilesPath/Unity-iPhone.build/Release-iphoneos/GameAssembly.build/Script-C62A2A42F32E085EF849CF0B.sh |
| 61 | + /Users/evgeniyasenchurova/Library/Developer/Xcode/DerivedData/Unity-iPhone-gtnilxmbqexxvtcauewfdmpfbvfe/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/IntermediateBuildFilesPath/Unity-iPhone.build/Release-iphoneos/GameAssembly.build/Script-C62A2A42F32E085EF849CF0B.sh: line 19: /Users/evgeniyasenchurova Downloads/ build_ios/Il2Cpp0utputProject/IL2CPP/build/deploy_arm64/il2cpp: Operation not permitted |
| 62 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 63 | + """ |
| 64 | + |
| 65 | + let result = parser.parse(input: output) |
| 66 | + |
| 67 | + XCTAssertEqual(result.summary.errors, 1, "Should detect PhaseScriptExecution failure for Unity") |
| 68 | + XCTAssertFalse(result.errors.isEmpty, "Should have at least one error") |
| 69 | + |
| 70 | + let error = result.errors[0] |
| 71 | + XCTAssertTrue( |
| 72 | + error.message.contains("Command PhaseScriptExecution failed"), |
| 73 | + "Error message should contain failure indicator" |
| 74 | + ) |
| 75 | + XCTAssertTrue( |
| 76 | + error.message.contains("Operation not permitted"), |
| 77 | + "Error message should include operation permission error" |
| 78 | + ) |
| 79 | + } |
| 80 | + |
| 81 | + func testPhaseScriptExecutionWithMultipleErrors() { |
| 82 | + let output = """ |
| 83 | + Build started... |
| 84 | +
|
| 85 | + Compiling Swift files... |
| 86 | + file.swift:10: error: Cannot find 'someFunction' in scope |
| 87 | +
|
| 88 | + Running post-build script... |
| 89 | + /bin/sh -c /path/to/script.sh |
| 90 | + Script execution failed |
| 91 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 92 | +
|
| 93 | + Build complete! |
| 94 | + """ |
| 95 | + |
| 96 | + let result = parser.parse(input: output) |
| 97 | + |
| 98 | + // Should detect both the compilation error and the PhaseScriptExecution failure |
| 99 | + XCTAssertEqual( |
| 100 | + result.summary.errors, |
| 101 | + 2, |
| 102 | + "Should detect both compilation error and PhaseScriptExecution failure" |
| 103 | + ) |
| 104 | + |
| 105 | + // Find the PhaseScriptExecution error |
| 106 | + let phaseError = result.errors.first { $0.message.contains("Command PhaseScriptExecution failed") } |
| 107 | + XCTAssertNotNil(phaseError, "Should have PhaseScriptExecution error") |
| 108 | + |
| 109 | + if let phaseError = phaseError { |
| 110 | + XCTAssertTrue( |
| 111 | + phaseError.message.contains("Script execution failed"), |
| 112 | + "Error message should include preceding context" |
| 113 | + ) |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + func testPhaseScriptExecutionWithSingleLineContext() { |
| 118 | + let output = """ |
| 119 | + Running build phase script... |
| 120 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 121 | + """ |
| 122 | + |
| 123 | + let result = parser.parse(input: output) |
| 124 | + |
| 125 | + XCTAssertEqual(result.summary.errors, 1, "Should detect PhaseScriptExecution failure with single context line") |
| 126 | + |
| 127 | + let error = result.errors[0] |
| 128 | + XCTAssertTrue( |
| 129 | + error.message.contains("Command PhaseScriptExecution failed"), |
| 130 | + "Error message should contain failure indicator" |
| 131 | + ) |
| 132 | + XCTAssertTrue( |
| 133 | + error.message.contains("Running build phase script"), |
| 134 | + "Error message should include context line" |
| 135 | + ) |
| 136 | + } |
| 137 | + |
| 138 | + func testPhaseScriptExecutionWithNoContext() { |
| 139 | + let output = """ |
| 140 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 141 | + """ |
| 142 | + |
| 143 | + let result = parser.parse(input: output) |
| 144 | + |
| 145 | + XCTAssertEqual(result.summary.errors, 1, "Should detect PhaseScriptExecution failure even with no context") |
| 146 | + |
| 147 | + let error = result.errors[0] |
| 148 | + XCTAssertTrue( |
| 149 | + error.message.contains("Command PhaseScriptExecution failed"), |
| 150 | + "Error message should contain failure indicator" |
| 151 | + ) |
| 152 | + } |
| 153 | + |
| 154 | + func testPhaseScriptExecutionDoesNotDuplicateErrors() { |
| 155 | + let output = """ |
| 156 | + /bin/sh -c /path/to/script.sh |
| 157 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 158 | + /bin/sh -c /path/to/script.sh |
| 159 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 160 | + """ |
| 161 | + |
| 162 | + let result = parser.parse(input: output) |
| 163 | + |
| 164 | + // Should deduplicate identical errors |
| 165 | + XCTAssertLessThanOrEqual( |
| 166 | + result.summary.errors, |
| 167 | + 2, |
| 168 | + "Should not duplicate identical PhaseScriptExecution errors" |
| 169 | + ) |
| 170 | + } |
| 171 | + |
| 172 | + func testBuildSucceededDoesNotCreatePhaseError() { |
| 173 | + let output = """ |
| 174 | + Running phase script... |
| 175 | + Build succeeded in 5.234 seconds |
| 176 | + """ |
| 177 | + |
| 178 | + let result = parser.parse(input: output) |
| 179 | + |
| 180 | + XCTAssertEqual(result.summary.errors, 0, "Build succeeded should not create a PhaseScriptExecution error") |
| 181 | + } |
| 182 | + |
| 183 | + func testPhaseScriptExecutionWithComplexOutput() { |
| 184 | + let output = """ |
| 185 | + ** BUILD START ** |
| 186 | +
|
| 187 | + Linking Framework/Module |
| 188 | +
|
| 189 | + Running script phase [CP] Copy Pods Resources |
| 190 | +
|
| 191 | + Resources copied... |
| 192 | + warning: Some resources were skipped |
| 193 | +
|
| 194 | + Running script phase custom build script |
| 195 | +
|
| 196 | + Processing configuration... |
| 197 | + /bin/sh -c /path/to/complex/script.sh |
| 198 | + Error: Configuration file not found at /expected/path |
| 199 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 200 | +
|
| 201 | + Build failed after 10.234 seconds |
| 202 | + """ |
| 203 | + |
| 204 | + let result = parser.parse(input: output) |
| 205 | + |
| 206 | + // Should detect the PhaseScriptExecution failure |
| 207 | + let phaseError = result.errors.first { $0.message.contains("Command PhaseScriptExecution failed") } |
| 208 | + XCTAssertNotNil(phaseError, "Should detect PhaseScriptExecution failure in complex output") |
| 209 | + |
| 210 | + if let phaseError = phaseError { |
| 211 | + XCTAssertTrue( |
| 212 | + phaseError.message.contains("Error: Configuration file not found"), |
| 213 | + "Should include relevant context from preceding lines" |
| 214 | + ) |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + func testPhaseScriptExecutionFiltersUnrelatedWarnings() { |
| 219 | + // Test case based on user's real project scenario |
| 220 | + let output = """ |
| 221 | + Warning: unknown environment variable SWIFT_DEBUG_INFORMATION_FORMAT |
| 222 | + bash: /Users/roman/Developer/SpaceTime/build_id.sh: No such file or directory |
| 223 | + Command PhaseScriptExecution failed with a nonzero exit code |
| 224 | + """ |
| 225 | + |
| 226 | + let result = parser.parse(input: output) |
| 227 | + |
| 228 | + XCTAssertEqual(result.summary.errors, 1, "Should detect PhaseScriptExecution failure") |
| 229 | + XCTAssertFalse(result.errors.isEmpty, "Should have at least one error") |
| 230 | + |
| 231 | + let error = result.errors[0] |
| 232 | + XCTAssertTrue( |
| 233 | + error.message.contains("Command PhaseScriptExecution failed"), |
| 234 | + "Error message should contain failure indicator" |
| 235 | + ) |
| 236 | + XCTAssertTrue( |
| 237 | + error.message.contains("bash:"), |
| 238 | + "Error message should include bash error context" |
| 239 | + ) |
| 240 | + XCTAssertTrue( |
| 241 | + error.message.contains("No such file or directory"), |
| 242 | + "Error message should include error details" |
| 243 | + ) |
| 244 | + XCTAssertFalse( |
| 245 | + error.message.contains("Warning: unknown environment variable"), |
| 246 | + "Should filter out unrelated Warning: lines" |
| 247 | + ) |
| 248 | + } |
| 249 | +} |
0 commit comments