Skip to content

Cleanup SPMTestSupport Module #3019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Nov 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6ae224a
Extract TestWorkspace related types into separate files
mattt Nov 4, 2020
98467a0
Remove unnecessary import statements
mattt Nov 4, 2020
3d6cdd9
Rename TestWorkspace to MockWorkspace
mattt Nov 4, 2020
24d3ad7
Rename TestTarget to MockTarget
mattt Nov 4, 2020
d9f3598
Rename TestProduct to MockProduct
mattt Nov 4, 2020
5316444
Rename TestDependency to MockDependency, replacing existing type of t…
mattt Nov 4, 2020
e5e5d09
Rename TestPackage to MockPackage, replacing existing type of that name
mattt Nov 4, 2020
f325695
Remove unused property in Destination extension
mattt Nov 4, 2020
3e48bc0
Remove unused constant globalSymbolInMainBinary
mattt Nov 4, 2020
a9ec7f6
Move XCTAssertEqual overload to XCTAssertHelpers
mattt Nov 4, 2020
b999b76
Remove redundant import statement
mattt Nov 4, 2020
f7d232d
Remove unused type MockPackageContainer2
mattt Nov 4, 2020
215d81b
Rename MockPackagesProvider to MockPackageContainerProvider
mattt Nov 4, 2020
1a94f0e
Remove unused resolveToVersion helper method in DependencyResolver ex…
mattt Nov 4, 2020
2f283f0
Rename MockGraph to MockDependencyGraph, replacing existing type of t…
mattt Nov 4, 2020
17293e6
Extract MockDependencyGraph to separate file
mattt Nov 4, 2020
0bf1885
Remove getContainer helper method overload
mattt Nov 4, 2020
a3a67f9
Remove ProductDescription helper initializer overload
mattt Nov 4, 2020
2ab1f34
Consolidate Manifest extensions
mattt Nov 4, 2020
07bb91e
Rename Manifest.swift to ManifestExtensions.swift
mattt Nov 4, 2020
4cf093e
Rename MockPackageConstraint to MockPackageContainer.Constraint
mattt Nov 4, 2020
041bb61
Replace use of MockWorkspace.PackageDependency with MockDependency
mattt Nov 4, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
*/

import PackageModel
import TSCBasic
import TSCUtility

extension Manifest {
public static func createV4Manifest(
public extension Manifest {
static func createV4Manifest(
name: String,
path: String = "/",
url: String = "/",
Expand Down Expand Up @@ -48,7 +48,7 @@ extension Manifest {
)
}

public static func createManifest(
static func createManifest(
name: String,
defaultLocalization: String? = nil,
platforms: [PlatformDescription] = [],
Expand Down Expand Up @@ -85,16 +85,8 @@ extension Manifest {
targets: targets
)
}
}

extension ProductDescription {
public init(name: String, targets: [String]) {
self.init(name: name, type: .library(.automatic), targets: targets)
}
}

extension Manifest {
public func with(url: String) -> Manifest {
func with(url: String) -> Manifest {
return Manifest(
name: name,
platforms: platforms,
Expand Down
44 changes: 44 additions & 0 deletions Sources/SPMTestSupport/MockDependency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2014 - 2020 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 PackageModel
import TSCBasic

public struct MockDependency {
public typealias Requirement = PackageDependencyDescription.Requirement

public let name: String?
public let path: String
public let requirement: Requirement
public let products: ProductFilter

public init(name: String, requirement: Requirement, products: ProductFilter = .everything) {
self.name = name
self.path = name
self.requirement = requirement
self.products = products
}

public init(name: String?, path: String, requirement: Requirement, products: ProductFilter = .everything) {
self.name = name
self.path = path
self.requirement = requirement
self.products = products
}

public func convert(baseURL: AbsolutePath) -> PackageDependencyDescription {
return PackageDependencyDescription(
name: self.name,
url: baseURL.appending(RelativePath(self.path)).pathString,
requirement: self.requirement,
productFilter: self.products
)
}
}
247 changes: 91 additions & 156 deletions Sources/SPMTestSupport/MockDependencyGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,183 +6,118 @@

See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

*/
import XCTest

import TSCBasic
import PackageLoading
import PackageModel
import PackageGraph
import SourceControl
import TSCUtility
import PackageModel
import TSCBasic
import struct TSCUtility.Version

/// Represents a mock package.
public struct MockPackage {
/// The name of the package.
public struct MockDependencyGraph {
public let name: String

/// The current available version of the package.
public let version: TSCUtility.Version?

/// The dependencies of the package.
public let dependencies: [MockDependency]

public init(_ name: String, version: TSCUtility.Version?, dependencies: [MockDependency] = []) {
self.name = name
self.version = version
self.dependencies = dependencies
public let constraints: [MockPackageContainer.Constraint]
public let containers: [MockPackageContainer]
public let result: [String: Version]

public func checkResult(
_ output: [(container: String, version: Version)],
file: StaticString = #file,
line: UInt = #line
) {
var result = self.result
for item in output {
XCTAssertEqual(result[item.container], item.version, file: file, line: line)
result[item.container] = nil
}
if !result.isEmpty {
XCTFail("Unchecked containers: \(result)", file: file, line: line)
}
}
}

/// Represents a mock package dependency.
public struct MockDependency {
/// The name of the dependency.
public let name: String
// MARK: - JSON

/// The allowed version range of this dependency.
public let version: Range<TSCUtility.Version>
public extension MockDependencyGraph {
init(_ json: JSON) {
guard case .dictionary(let dict) = json else { fatalError() }
guard case .string(let name)? = dict["name"] else { fatalError() }
guard case .array(let constraints)? = dict["constraints"] else { fatalError() }
guard case .array(let containers)? = dict["containers"] else { fatalError() }
guard case .dictionary(let result)? = dict["result"] else { fatalError() }

public init(_ name: String, version: Range<TSCUtility.Version>) {
self.name = name
self.version = version
}

public init(_ name: String, version: TSCUtility.Version) {
self.result = Dictionary(uniqueKeysWithValues: result.map { value in
let (container, version) = value
guard case .string(let str) = version else { fatalError() }
return (container.lowercased(), Version(string: str)!)
})
self.name = name
self.version = version..<Version(version.major, version.minor, version.patch + 1)
self.constraints = constraints.map(PackageContainerConstraint.init(json:))
self.containers = containers.map(MockPackageContainer.init(json:))
}
}

/// A mock manifest graph creator. It takes in a path where it creates empty repositories for mock packages.
/// For each mock package, it creates a manifest and maps it to the url and that version in mock manifest loader.
/// It provides basic functionality of getting the repo paths and manifests which can be later modified in tests.
public struct MockManifestGraph {
/// The map of repositories created by this class where the key is name of the package.
public let repos: [String: RepositorySpecifier]

/// The generated mock manifest loader.
public let manifestLoader: MockManifestLoader

/// The generated root manifest.
public let rootManifest: Manifest

/// The map of external manifests created.
public let manifests: [MockManifestLoader.Key: Manifest]

/// Present if file system used is in inmemory.
public let repoProvider: InMemoryGitRepositoryProvider?

/// Convinience accessor for repository specifiers.
public func repo(_ package: String) -> RepositorySpecifier {
return repos[package]!
}
private extension MockPackageContainer {
convenience init(json: JSON) {
guard case .dictionary(let dict) = json else { fatalError() }
guard case .string(let identifier)? = dict["identifier"] else { fatalError() }
guard case .dictionary(let versions)? = dict["versions"] else { fatalError() }

var depByVersion: [Version: [(container: String, versionRequirement: VersionSetSpecifier)]] = [:]
for (version, deps) in versions {
guard case .array(let depArray) = deps else { fatalError() }
depByVersion[Version(string: version)!] = depArray
.map(PackageContainerConstraint.init(json:))
.map { constraint in
switch constraint.requirement {
case .versionSet(let versionSet):
return (constraint.identifier.identity, versionSet)
case .unversioned:
fatalError()
case .revision:
fatalError()
}
}
}

/// Convinience accessor for external manifests.
public func manifest(_ package: String, version: TSCUtility.Version) -> Manifest {
return manifests[MockManifestLoader.Key(url: repo(package).url, version: version)]!
self.init(name: identifier, dependenciesByVersion: depByVersion)
}
}

/// Create instance with mocking on in memory file system.
public init(
at path: AbsolutePath,
rootDeps: [MockDependency],
packages: [MockPackage],
fs: InMemoryFileSystem
) throws {
try self.init(at: path, rootDeps: rootDeps, packages: packages, inMemory: (fs, InMemoryGitRepositoryProvider()))
private extension MockPackageContainer.Constraint {
init(json: JSON) {
guard case .dictionary(let dict) = json else { fatalError() }
guard case .string(let identifier)? = dict["identifier"] else { fatalError() }
guard let requirement = dict["requirement"] else { fatalError() }
let products: ProductFilter = try! JSON(dict).get("products")
let id = PackageReference(identity: identifier.lowercased(), path: "", kind: .remote)
self.init(container: id, versionRequirement: VersionSetSpecifier(requirement), products: products)
}
}

public init(
at path: AbsolutePath,
rootDeps: [MockDependency],
packages: [MockPackage],
inMemory: (fs: InMemoryFileSystem, provider: InMemoryGitRepositoryProvider)? = nil
) throws {
repoProvider = inMemory?.provider
// Create the test repositories, we don't need them to have actual
// contents (the manifests are mocked).
let repos = Dictionary(uniqueKeysWithValues: try packages.map({ package -> (String, RepositorySpecifier) in
let repoPath = path.appending(component: package.name)
let tag = package.version?.description ?? "initial"
let specifier = RepositorySpecifier(url: repoPath.pathString)

// If this is in memory mocked graph.
if let inMemory = inMemory {
if !inMemory.fs.exists(repoPath) {
let repo = InMemoryGitRepository(path: repoPath, fs: inMemory.fs)
try repo.createDirectory(repoPath, recursive: true)
let filePath = repoPath.appending(component: "source.swift")
try repo.writeFileContents(filePath, bytes: "foo")
repo.commit()
try repo.tag(name: tag)
inMemory.provider.add(specifier: specifier, repository: repo)
}
} else {
// Don't recreate repo if it is already there.
if !localFileSystem.exists(repoPath) {
try makeDirectories(repoPath)
initGitRepo(repoPath, tag: package.version?.description ?? "initial")
private extension VersionSetSpecifier {
init(_ json: JSON) {
switch json {
case .string(let str):
switch str {
case "any": self = .any
case "empty": self = .empty
default: fatalError()
}
case .array(let arr):
switch arr.count {
case 1:
guard case .string(let str) = arr[0] else { fatalError() }
self = .exact(Version(string: str)!)
case 2:
let versions = arr.map { json -> Version in
guard case .string(let str) = json else { fatalError() }
return Version(string: str)!
}
self = .range(versions[0] ..< versions[1])
default: fatalError()
}
return (package.name, specifier)
}))

let src = path.appending(component: "Sources")
if let fs = inMemory?.fs {
try fs.createDirectory(src, recursive: true)
try fs.writeFileContents(src.appending(component: "foo.swift"), bytes: "")
} else {
// Make a sources folder for our root package.
try makeDirectories(src)
try systemQuietly(["touch", src.appending(component: "foo.swift").pathString])
default: fatalError()
}

// Create the root manifest.
rootManifest = Manifest(
name: "Root",
platforms: [],
path: path.appending(component: Manifest.filename),
url: path.pathString,
version: nil,
toolsVersion: .v4,
packageKind: .root,
dependencies: MockManifestGraph.createDependencies(repos: repos, dependencies: rootDeps)
)

// Create the manifests from mock packages.
var manifests = Dictionary(uniqueKeysWithValues: packages.map({ package -> (MockManifestLoader.Key, Manifest) in
let url = repos[package.name]!.url
let manifest = Manifest(
name: package.name,
platforms: [],
path: AbsolutePath(url).appending(component: Manifest.filename),
url: url,
version: package.version,
toolsVersion: .v4,
packageKind: .remote,
dependencies: MockManifestGraph.createDependencies(repos: repos, dependencies: package.dependencies)
)
return (MockManifestLoader.Key(url: url, version: package.version), manifest)
}))
// Add the root manifest.
manifests[MockManifestLoader.Key(url: path.pathString, version: nil)] = rootManifest

manifestLoader = MockManifestLoader(manifests: manifests)
self.manifests = manifests
self.repos = repos
}

/// Maps MockDependencies into PackageDescription's Dependency array.
private static func createDependencies(
repos: [String: RepositorySpecifier],
dependencies: [MockDependency]
) -> [PackageDependencyDescription] {
return dependencies.map({ dependency in
return PackageDependencyDescription(
name: dependency.name,
url: repos[dependency.name]?.url ?? "//\(dependency.name)",
requirement: .range(dependency.version.lowerBound ..< dependency.version.upperBound))
})
}
}
Loading