diff --git a/Sources/_InternalTestSupport/CombinationsWithRepetition.swift b/Sources/_InternalTestSupport/CombinationsWithRepetition.swift new file mode 100644 index 00000000000..4855780db63 --- /dev/null +++ b/Sources/_InternalTestSupport/CombinationsWithRepetition.swift @@ -0,0 +1,59 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +import Foundation +package struct CombinationsWithRepetition : Sequence { + + let base: C + let length: Int + + init(of base: C, length: Int) { + self.base = base + self.length = length + } + + package struct Iterator : IteratorProtocol { + let base: C + + var firstIteration = true + var finished: Bool + var positions: [C.Index] + + package init(of base: C, length: Int) { + self.base = base + finished = base.isEmpty + positions = Array(repeating: base.startIndex, count: length) + } + + package mutating func next() -> [C.Element]? { + if firstIteration { + firstIteration = false + } else { + // Update indices for next combination. + finished = true + for i in positions.indices.reversed() { + base.formIndex(after: &positions[i]) + if positions[i] != base.endIndex { + finished = false + break + } else { + positions[i] = base.startIndex + } + } + + } + return finished ? nil : positions.map { base[$0] } + } + } + + package func makeIterator() -> Iterator { + return Iterator(of: base, length: length) + } +} diff --git a/Sources/_InternalTestSupport/ProcessInfo+hostutils.swift b/Sources/_InternalTestSupport/ProcessInfo+hostutils.swift new file mode 100644 index 00000000000..2fbb61ec736 --- /dev/null +++ b/Sources/_InternalTestSupport/ProcessInfo+hostutils.swift @@ -0,0 +1,37 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ +import Foundation + +extension ProcessInfo { + public static func isHostAmazonLinux2(_ content: String? = nil) -> Bool { + let contentString: String + if let content { + contentString = content + } else { + let osReleasePath = "/etc/os-release" + do { + contentString = try String(contentsOfFile: osReleasePath, encoding: .utf8) + } catch { + return false + } + } + let lines = contentString.components(separatedBy: .newlines) + for line in lines { + if line.starts(with: "ID=") { + let id = line.replacingOccurrences(of: "ID=", with: "").trimmingCharacters(in: .whitespacesAndNewlines) + if id == "amzn" { // ID for Amazon Linux is "amzn" + return true + } + } + } + return false + } + +} \ No newline at end of file diff --git a/Tests/BasicsTests/Environment/EnvironmentTests.swift b/Tests/BasicsTests/Environment/EnvironmentTests.swift index b7011b2dd14..693491de6ec 100644 --- a/Tests/BasicsTests/Environment/EnvironmentTests.swift +++ b/Tests/BasicsTests/Environment/EnvironmentTests.swift @@ -148,7 +148,7 @@ struct EnvironmentTests { /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. @Test( - .disabled(if: isInCiEnvironment || CiEnvironment.runningInSelfHostedPipeline, "This test can disrupt other tests running in parallel."), + .disabled(if: CiEnvironment.runningInSmokeTestPipeline || CiEnvironment.runningInSelfHostedPipeline, "This test can disrupt other tests running in parallel."), ) func makeCustomPathEnv() async throws { let customEnvironment: Environment = .current diff --git a/Tests/BasicsTests/ProcessInfoTests.swift b/Tests/BasicsTests/ProcessInfoTests.swift new file mode 100644 index 00000000000..0b795d483b3 --- /dev/null +++ b/Tests/BasicsTests/ProcessInfoTests.swift @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import Foundation +import Basics +import Testing +@testable import struct _InternalTestSupport.CombinationsWithRepetition + +fileprivate let d = [ + [], + [""], + ["line1"], + ["line1", "line2"], + ["line1", "line2", "line3"], + ] +fileprivate let prefixAndSuffixData = CombinationsWithRepetition(of: d, length: 2).map( {data in + // Content(prefix: data.0, suffix: data.1) + Content(prefix: data[0], suffix: data[1]) +}) + +fileprivate struct Content { + let prefix: [String] + let suffix: [String] + + init(prefix pre: [String], suffix: [String]) { + self.prefix = pre + self.suffix = suffix + } + + func getContent(_ value: String) -> String { + let contentArray: [String] = self.prefix + [value] + self.suffix + let content = contentArray.joined(separator: "\n") + return content + } +} + +@Suite +struct ProcessInfoExtensionTests { + + @Suite + struct isAmazonLinux2 { + @Test( + arguments: [ + (contentUT: "", expected: false), + (contentUT: "ID=", expected: false), + (contentUT: "ID=foo", expected: false), + (contentUT: "ID=amzn", expected: true), + (contentUT: " ID=amzn", expected: false), + ], prefixAndSuffixData, + ) + fileprivate func isAmazonLinux2ReturnsExpectedValue( + data: (contentUT: String, expected: Bool), + content: Content, + ) async throws { + let content = content.getContent(data.contentUT) + + let actual = ProcessInfo.isHostAmazonLinux2(content) + + #expect(actual == data.expected, "Content is: '\(content)'") + } + + @Test( + "isHostAmazonLinux2 returns false when not executed on Linux", + .skipHostOS(.linux), + .tags(Tag.TestSize.medium), + ) + func isAmazonLinux2ReturnsFalseWhenNotRunOnLinux() { + let actual = ProcessInfo.isHostAmazonLinux2() + + #expect(actual == false) + } + } +} \ No newline at end of file diff --git a/Tests/CommandsTests/APIDiffTests.swift b/Tests/CommandsTests/APIDiffTests.swift index e3271bc2f96..b2f509740d4 100644 --- a/Tests/CommandsTests/APIDiffTests.swift +++ b/Tests/CommandsTests/APIDiffTests.swift @@ -103,7 +103,11 @@ struct APIDiffTests { } } - @Test(.requiresAPIDigester, arguments: SupportedBuildSystemOnAllPlatforms) + @Test( + .requiresAPIDigester, + .issue("https://github.com/swiftlang/swift-package-manager/issues/8926", relationship: .defect), + arguments: SupportedBuildSystemOnAllPlatforms, + ) func testMultiTargetAPIDiff(buildSystem: BuildSystemProvider.Kind) async throws { try await fixture(name: "Miscellaneous/APIDiff/") { fixturePath in let packageRoot = fixturePath.appending("Bar") @@ -116,11 +120,15 @@ struct APIDiffTests { string: "public class Qux { private let x = 1 }" ) try await expectThrowsCommandExecutionError(try await execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot, buildSystem: buildSystem)) { error in - #expect(error.stdout.contains("2 breaking changes detected in Qux")) - #expect(error.stdout.contains("💔 API breakage: class Qux has generic signature change from to ")) - #expect(error.stdout.contains("💔 API breakage: var Qux.x has been removed")) - #expect(error.stdout.contains("1 breaking change detected in Baz")) - #expect(error.stdout.contains("💔 API breakage: func bar() has been removed")) + withKnownIssue { + #expect(error.stdout.contains("2 breaking changes detected in Qux")) + #expect(error.stdout.contains("💔 API breakage: class Qux has generic signature change from to ")) + #expect(error.stdout.contains("💔 API breakage: var Qux.x has been removed")) + #expect(error.stdout.contains("1 breaking change detected in Baz")) + #expect(error.stdout.contains("💔 API breakage: func bar() has been removed")) + } when: { + buildSystem == .swiftbuild && ProcessInfo.isHostAmazonLinux2() + } } } } @@ -156,7 +164,11 @@ struct APIDiffTests { } } - @Test(.requiresAPIDigester, arguments: SupportedBuildSystemOnAllPlatforms) + @Test( + .requiresAPIDigester, + .issue("https://github.com/swiftlang/swift-package-manager/issues/8926", relationship: .defect), + arguments: SupportedBuildSystemOnAllPlatforms, + ) func testCheckVendedModulesOnly(buildSystem: BuildSystemProvider.Kind) async throws { try await fixture(name: "Miscellaneous/APIDiff/") { fixturePath in let packageRoot = fixturePath.appending("NonAPILibraryTargets") @@ -177,11 +189,15 @@ struct APIDiffTests { string: "public class Qux { private let x = 1 }" ) try await expectThrowsCommandExecutionError(try await execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot, buildSystem: buildSystem)) { error in - #expect(error.stdout.contains("💔 API breakage")) - let regex = try Regex("\\d+ breaking change(s?) detected in Foo") - #expect(error.stdout.contains(regex)) - #expect(error.stdout.contains(regex)) - #expect(error.stdout.contains(regex)) + try withKnownIssue { + #expect(error.stdout.contains("💔 API breakage")) + let regex = try Regex("\\d+ breaking change(s?) detected in Foo") + #expect(error.stdout.contains(regex)) + #expect(error.stdout.contains(regex)) + #expect(error.stdout.contains(regex)) + } when: { + buildSystem == .swiftbuild && ProcessInfo.isHostAmazonLinux2() + } // Qux is not part of a library product, so any API changes should be ignored #expect(!error.stdout.contains("Qux"))