-
Notifications
You must be signed in to change notification settings - Fork 204
Some Improvements to Cross-Module Incremental Build Testing #541
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
//===---------------- Antisymmetry.swift - Swift Testing ------------------===// | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import XCTest | ||
import IncrementalTestFramework | ||
|
||
// This test establishes an "antisymmetric" chain of modules that import one | ||
// another and ensures that rebuilds propagate in one direction. | ||
// | ||
// Module B Module A | ||
// -------- -> -------- | ||
// ^ x | ||
// | | | ||
// ----XXX----- | ||
class AntisymmetryTest: XCTestCase { | ||
private let defAdditions = [ | ||
"b-add-struct", | ||
"b-add-class", | ||
"b-add-protocol", | ||
"b-add-extension", | ||
] | ||
|
||
private let useAdditions = [ | ||
"use-struct", | ||
"use-class", | ||
"use-protocol", | ||
"use-extension", | ||
] | ||
|
||
func testAntisymmetricTopLevelDefs() throws { | ||
try IncrementalTest.perform([ | ||
// The baseline step is special, we want everything to get built first. | ||
Step(adding: defAdditions, | ||
building: [ .B, .A ], | ||
.expecting(.init(always: [ .main, .B ]))) | ||
] + defAdditions.dropFirst().indices.map { idx in | ||
// Make sure the addition of defs without users only causes cascading | ||
// rebuilds when incremental imports are disabled. | ||
Step(adding: Array(defAdditions[0..<idx]), | ||
building: [ .B, .A ], | ||
.expecting(.init(always: [ .B ], andWhenDisabled: [ .main ]))) | ||
}) | ||
} | ||
|
||
func testAntisymmetricTopLevelUses() throws { | ||
try IncrementalTest.perform([ | ||
// The baseline step is special, we want everything to get built first. | ||
Step(adding: defAdditions, | ||
building: [ .B, .A ], | ||
.expecting(.init(always: [ .main, .B ]))) | ||
] + useAdditions.indices.dropFirst().map { idx in | ||
// Make sure the addition of uses causes only users to recompile. | ||
Step(adding: defAdditions + Array(useAdditions[0..<idx]), | ||
building: [ .B, .A ], | ||
.expecting(.init(always: [ .main ]))) | ||
}) | ||
} | ||
} | ||
|
||
fileprivate extension Module { | ||
static var A = Module(named: "A", containing: [ | ||
.main, | ||
], importing: [ | ||
.B, | ||
], producing: .executable) | ||
|
||
static var B = Module(named: "B", containing: [ | ||
.B, | ||
], producing: .library) | ||
} | ||
|
||
fileprivate extension Source { | ||
static var main: Source { | ||
Self( | ||
named: "main", | ||
containing: """ | ||
import B | ||
|
||
//# use-struct _ = BStruct() | ||
//# use-class _ = BClass() | ||
//# use-protocol extension BStruct: BProtocol { public func foo(parameter: Int = 0) {} } | ||
//# use-extension BStruct().foo() | ||
""") | ||
} | ||
} | ||
|
||
fileprivate extension Source { | ||
static var B: Source { | ||
Self( | ||
named: "B", | ||
containing: """ | ||
//# b-add-struct public struct BStruct { public init() {} } | ||
//# b-add-class public class BClass { public init() {} } | ||
//# b-add-protocol public protocol BProtocol {} | ||
//# b-add-extension extension BStruct { public func foo() {} } | ||
""") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
//===---------------- Transitivity.swift - Swift Testing ------------------===// | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import XCTest | ||
import IncrementalTestFramework | ||
|
||
// This test establishes a "transitive" chain of modules that import one another | ||
// and ensures that a cross-module incremental build rebuilds all modules | ||
// involved in the chain. | ||
// | ||
// Module C Module B Module A | ||
// -------- -> -------- -> -------- | ||
// | ^ | ||
// | | | ||
// ------------------------- | ||
class TransitivityTest: XCTestCase { | ||
func testTransitiveTopLevelUses() throws { | ||
try IncrementalTest.perform([ | ||
// Build a baseline | ||
Step(adding: "transitive-baseline", | ||
building: [.C, .B, .A], | ||
.expecting([.C, .B, .A].allSourcesToCompile)), | ||
// Swap in a new default argument: B needs to rebuild `fromB` and | ||
// relink against fromC, but A doesn't import `C` so only non-incremental | ||
// imports rebuilds it. | ||
Step(adding: "transitive-add-default", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [.C, .B], andWhenDisabled: [.A]))), | ||
// Now change C back to the old baseline. We edit A in the process to | ||
// introduce a dependency on C, so it needs to rebuild. | ||
Step(adding: "transitive-baseline", "transitive-add-use-in-A", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [.C, .B, .A]))), | ||
// Same as before - the addition of a default argument requires B rebuild, | ||
// but A doesn't use anything from C, so it doesn't rebuild unless | ||
// incremental imports are disabled. | ||
Step(adding: "transitive-add-default", "transitive-add-use-in-A", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [.C, .B], andWhenDisabled: [.A]))), | ||
]) | ||
} | ||
|
||
func testTransitiveStructMember() throws { | ||
try IncrementalTest.perform([ | ||
// Establish the baseline build | ||
Step(adding: "transitive-baseline", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [ .A, .B, .C ]))), | ||
// Add the def of a struct to C, which B imports and has a use of so | ||
// B rebuilds but A does not unless incremental imports are disabled. | ||
Step(adding: "transitive-baseline", "transitive-struct-def-in-C", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [ .B, .C ], andWhenDisabled: [ .A ]))), | ||
// Now add a use in B, make sure C doesn't rebuild. | ||
Step(adding: "transitive-baseline", "transitive-struct-def-in-C", "transitive-struct-def-in-B", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [ .B, ], andWhenDisabled: [ .A ]))), | ||
// Now add a use in A and make sure only A rebuilds. | ||
Step(adding: "transitive-baseline", "transitive-struct-def-in-C", "transitive-struct-def-in-B", "transitive-struct-def-in-A", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [ .A ]))), | ||
// Finally, add a member to a struct in C, which influences the layout of | ||
// the struct in B, which influences the layout of the struct in A. | ||
// Everything rebuilds! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great to see this test! |
||
Step(adding: "transitive-baseline", "transitive-struct-add-member-in-C", "transitive-struct-def-in-B", "transitive-struct-def-in-A", | ||
building: [.C, .B, .A], | ||
.expecting(.init(always: [ .A, .B, .C ]))), | ||
]) | ||
} | ||
} | ||
|
||
fileprivate extension Module { | ||
static var A = Module(named: "A", containing: [ | ||
.A, | ||
], importing: [ | ||
.B, .C, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I read this--haven't absorbed the whole thing yet--I'm wondering why A also imports C? Or would that be a different test we might need? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm... if the import is optional, maybe there should be different There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a slightly tighter test without the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'll also be noisier because the change in search paths will force us to discard the incremental build state. We need separate tests for that case if we don't have them already. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent point! Thank you for thinking of it. |
||
], producing: .executable) | ||
|
||
static var B = Module(named: "B", containing: [ | ||
.B, | ||
], importing: [ | ||
.C | ||
], producing: .library) | ||
|
||
static var C = Module(named: "C", containing: [ | ||
.C, | ||
], producing: .library) | ||
} | ||
|
||
fileprivate extension Source { | ||
static var A: Source { | ||
Self( | ||
named: "A", | ||
containing: """ | ||
import B | ||
|
||
//# transitive-add-use-in-A import C | ||
//# transitive-add-use-in-A public func fromA() { | ||
//# transitive-add-use-in-A return fromB() | ||
//# transitive-add-use-in-A } | ||
|
||
//# transitive-struct-def-in-A import C | ||
//# transitive-struct-def-in-A struct AStruct { var b: BStruct } | ||
""") | ||
} | ||
} | ||
|
||
fileprivate extension Source { | ||
static var B: Source { | ||
Self( | ||
named: "B", | ||
containing: """ | ||
import C | ||
|
||
public func fromB() { | ||
return fromC() | ||
} | ||
|
||
//# transitive-struct-def-in-B public struct BStruct { var c: CStruct } | ||
""") | ||
} | ||
} | ||
|
||
fileprivate extension Source { | ||
static var C: Source { | ||
Self( | ||
named: "C", | ||
containing: """ | ||
//# transitive-baseline public func fromC() {} | ||
//# transitive-add-default public func fromC(parameter: Int = 0) {} | ||
|
||
//# transitive-struct-def-in-C public struct CStruct { } | ||
//# transitive-struct-add-member-in-C public struct CStruct { var x: Int } | ||
""") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole file is beautiful! Well-done!! I wonder if, in the future, we'll want to add other sorts of uses and definitions, and/or test removals as well?