Skip to content

Commit

Permalink
Add integration tests for package dependencies
Browse files Browse the repository at this point in the history
- Verify various contract calls with spm
- Exercises use local version workflow

Issue: swiftlang#1052
  • Loading branch information
michael-weng committed Oct 16, 2024
1 parent 48c562a commit 3b174b9
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 94 deletions.
Binary file added assets/test/Swift-Markdown/.DS_Store
Binary file not shown.
40 changes: 40 additions & 0 deletions assets/test/Swift-Markdown/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"configurations": [
{
"type": "swift-lldb",
"request": "launch",
"name": "Debug package1",
"program": "${workspaceFolder:defaultPackage}/.build/debug/package1",
"args": [],
"cwd": "${workspaceFolder:defaultPackage}",
"preLaunchTask": "swift: Build Debug package1"
},
{
"type": "swift-lldb",
"request": "launch",
"name": "Release package1",
"program": "${workspaceFolder:defaultPackage}/.build/release/package1",
"args": [],
"cwd": "${workspaceFolder:defaultPackage}",
"preLaunchTask": "swift: Build Release package1"
},
{
"type": "swift-lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:defaultpackage}",
"name": "Debug PackageExe",
"program": "${workspaceFolder:defaultpackage}/.build/debug/PackageExe",
"preLaunchTask": "swift: Build Debug PackageExe"
},
{
"type": "swift-lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:defaultpackage}",
"name": "Release PackageExe",
"program": "${workspaceFolder:defaultpackage}/.build/release/PackageExe",
"preLaunchTask": "swift: Build Release PackageExe"
}
]
}
23 changes: 23 additions & 0 deletions assets/test/Swift-Markdown/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// swift-tools-version:6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
// FIXME: Can be changed back to Swift-Markdown when
// https://github.com/swiftlang/swift-package-manager/issues/7931
// is released in the toolchain
name: "swift-markdown",
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "PackageLib",
targets: ["PackageLib"]),
],
targets: [
.target(
name: "PackageLib",
dependencies: []
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public let a = "B"
2 changes: 1 addition & 1 deletion assets/test/dependencies/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let package = Package(
targets: [
.executableTarget(
name: "dependencies",
dependencies: [],
dependencies: [.product(name: "PackageLib", package: "Swift-Markdown")],
path: "Sources"),
]
)
4 changes: 3 additions & 1 deletion assets/test/dependencies/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
import PackageLib

print("dependencies")
print("Test Asset:(dependencies)")
print(a)
5 changes: 4 additions & 1 deletion src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export async function folderCleanBuild(folderContext: FolderContext) {
/**
* Executes a {@link vscode.Task task} to debug swift target.
*/
async function debugBuildWithOptions(ctx: WorkspaceContext, options: vscode.DebugSessionOptions) {
export async function debugBuildWithOptions(
ctx: WorkspaceContext,
options: vscode.DebugSessionOptions
) {
const current = ctx.currentFolder;
if (!current) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/commands/dependencies/unedit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function uneditDependency(identifier: string, ctx: WorkspaceContext
});
}

async function uneditFolderDependency(
export async function uneditFolderDependency(
folder: FolderContext,
identifier: string,
ctx: WorkspaceContext,
Expand Down
257 changes: 167 additions & 90 deletions test/integration-tests/ui/PackageDependencyProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,107 +13,184 @@
//===----------------------------------------------------------------------===//

import { expect } from "chai";
import * as vscode from "vscode";
import {
PackageDependenciesProvider,
PackageNode,
} from "../../../src/ui/PackageDependencyProvider";
import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test";
import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities";
import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider";
import { testAssetPath } from "../../fixtures";
import { testAssetPath, testAssetUri } from "../../fixtures";
import { Version } from "../../../src/utilities/version";
import { FolderContext } from "../../../src/FolderContext";
import { WorkspaceContext } from "../../../src/WorkspaceContext";
import { resolveDependencies } from "../../../src/commands/dependencies/resolve";
import { updateDependencies } from "../../../src/commands/dependencies/update";
import { useLocalDependency } from "../../../src/commands/dependencies/useLocal";
import { uneditFolderDependency } from "../../../src/commands/dependencies/unedit";
import { resetPackage } from "../../../src/commands/resetPackage";
import * as utilities from "../../../src/commands/utilities";
import * as sinon from "sinon";
import { mockGlobalObject } from "../../MockUtils";

suite("PackageDependencyProvider Test Suite", function () {
let treeProvider: PackageDependenciesProvider;
this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build

suiteSetup(async function () {
const workspaceContext = await globalWorkspaceContextPromise;
// workspace-state.json was not introduced until swift 5.7
if (workspaceContext.toolchain.swiftVersion.isLessThan(new Version(5, 7, 0))) {
this.skip();
}
await waitForNoRunningTasks();
const folderContext = await folderContextPromise("dependencies");
await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask);
await workspaceContext.focusFolder(folderContext);
treeProvider = new PackageDependenciesProvider(workspaceContext);
// Allow up to 2 minutes to build,
// full workflow's interaction with spm is also longer than the default timeout
this.timeout(2 * 60 * 1000);

suite("PackageDependencyProvider Tree Node Tests", function () {
let treeProvider: PackageDependenciesProvider;

suiteSetup(async function () {
const workspaceContext = await globalWorkspaceContextPromise;
// workspace-state.json was not introduced until swift 5.7
if (workspaceContext.toolchain.swiftVersion.isLessThan(new Version(5, 7, 0))) {
this.skip();
}
await waitForNoRunningTasks();
const folderContext = await folderContextPromise("dependencies");
await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask);
await workspaceContext.focusFolder(folderContext);
treeProvider = new PackageDependenciesProvider(workspaceContext);
});

suiteTeardown(() => {
treeProvider?.dispose();
});

test("Includes remote dependency", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "swift-markdown") as PackageNode;
expect(dep).to.not.be.undefined;
expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git");
expect(dep?.path).to.equal(
`${testAssetPath("dependencies")}/.build/checkouts/swift-markdown`
);
});

test("Includes local dependency", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "defaultpackage") as PackageNode;
expect(dep).to.not.be.undefined;
expect(dep?.location).to.equal(testAssetPath("defaultPackage"));
expect(dep?.path).to.equal(testAssetPath("defaultPackage"));
});

test("Lists local dependency file structure", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "defaultpackage") as PackageNode;
expect(dep).to.not.be.undefined;

const folders = await treeProvider.getChildren(dep);
const folder = folders.find(n => n.name === "Sources");
expect(folder).to.not.be.undefined;

expect(folder?.path).to.equal(`${testAssetPath("defaultPackage")}/Sources`);

const childFolders = await treeProvider.getChildren(folder);
const childFolder = childFolders.find(n => n.name === "PackageExe");
expect(childFolder).to.not.be.undefined;

expect(childFolder?.path).to.equal(
`${testAssetPath("defaultPackage")}/Sources/PackageExe`
);

const files = await treeProvider.getChildren(childFolder);
const file = files.find(n => n.name === "main.swift");
expect(file).to.not.be.undefined;

expect(file?.path).to.equal(
`${testAssetPath("defaultPackage")}/Sources/PackageExe/main.swift`
);
});

test("Lists remote dependency file structure", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "swift-markdown") as PackageNode;
expect(dep).to.not.be.undefined;

const folders = await treeProvider.getChildren(dep);
const folder = folders.find(n => n.name === "Sources");
expect(folder).to.not.be.undefined;

const path = `${testAssetPath("dependencies")}/.build/checkouts/swift-markdown`;
expect(folder?.path).to.equal(`${path}/Sources`);

const childFolders = await treeProvider.getChildren(folder);
const childFolder = childFolders.find(n => n.name === "CAtomic");
expect(childFolder).to.not.be.undefined;

expect(childFolder?.path).to.equal(`${path}/Sources/CAtomic`);

const files = await treeProvider.getChildren(childFolder);
const file = files.find(n => n.name === "CAtomic.c");
expect(file).to.not.be.undefined;

expect(file?.path).to.equal(`${path}/Sources/CAtomic/CAtomic.c`);
});
});

suiteTeardown(() => {
treeProvider?.dispose();
});

test("Includes remote dependency", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "swift-markdown") as PackageNode;
expect(dep).to.not.be.undefined;
expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git");
expect(dep?.path).to.equal(
`${testAssetPath("dependencies")}/.build/checkouts/swift-markdown`
);
});

test("Includes local dependency", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "defaultpackage") as PackageNode;
expect(dep).to.not.be.undefined;
expect(dep?.location).to.equal(testAssetPath("defaultPackage"));
expect(dep?.path).to.equal(testAssetPath("defaultPackage"));
});

test("Lists local dependency file structure", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "defaultpackage") as PackageNode;
expect(dep).to.not.be.undefined;

const folders = await treeProvider.getChildren(dep);
const folder = folders.find(n => n.name === "Sources");
expect(folder).to.not.be.undefined;

expect(folder?.path).to.equal(`${testAssetPath("defaultPackage")}/Sources`);

const childFolders = await treeProvider.getChildren(folder);
const childFolder = childFolders.find(n => n.name === "PackageExe");
expect(childFolder).to.not.be.undefined;

expect(childFolder?.path).to.equal(`${testAssetPath("defaultPackage")}/Sources/PackageExe`);

const files = await treeProvider.getChildren(childFolder);
const file = files.find(n => n.name === "main.swift");
expect(file).to.not.be.undefined;

expect(file?.path).to.equal(
`${testAssetPath("defaultPackage")}/Sources/PackageExe/main.swift`
);
});

test("Lists remote dependency file structure", async () => {
const items = await treeProvider.getChildren();

const dep = items.find(n => n.name === "swift-markdown") as PackageNode;
expect(dep).to.not.be.undefined;

const folders = await treeProvider.getChildren(dep);
const folder = folders.find(n => n.name === "Sources");
expect(folder).to.not.be.undefined;

const path = `${testAssetPath("dependencies")}/.build/checkouts/swift-markdown`;
expect(folder?.path).to.equal(`${path}/Sources`);

const childFolders = await treeProvider.getChildren(folder);
const childFolder = childFolders.find(n => n.name === "CAtomic");
expect(childFolder).to.not.be.undefined;

expect(childFolder?.path).to.equal(`${path}/Sources/CAtomic`);

const files = await treeProvider.getChildren(childFolder);
const file = files.find(n => n.name === "CAtomic.c");
expect(file).to.not.be.undefined;

expect(file?.path).to.equal(`${path}/Sources/CAtomic/CAtomic.c`);
suite("Full work flow tests", function () {
let folderContext: FolderContext;
let workspaceContext: WorkspaceContext;
const windowMock = mockGlobalObject(vscode, "window");

suiteSetup(async function () {
workspaceContext = await globalWorkspaceContextPromise;
await waitForNoRunningTasks();
folderContext = await folderContextPromise("dependencies");
await workspaceContext.focusFolder(folderContext);
});

test("Use local dependency", async () => {
// Expect to fail without setting up local version
const tasks = (await getBuildAllTask(folderContext)) as SwiftTask;
let { exitCode, output } = await executeTaskAndWaitForResult(tasks);
expect(exitCode).to.not.equal(0);
expect(output).to.include("PackageLib");
expect(output).to.include("required");

// Contract: spm reset, resolve, update should work
const executeTaskSpy = sinon.spy(utilities, "executeTaskWithUI");
await resolveDependencies(workspaceContext);
await expect(executeTaskSpy.returnValues[0]).to.eventually.be.true;

await updateDependencies(workspaceContext);
await expect(executeTaskSpy.returnValues[1]).to.eventually.be.true;

await resetPackage(workspaceContext);
await expect(executeTaskSpy.returnValues[2]).to.eventually.be.true;
await expect(executeTaskSpy.returnValues[3]).to.eventually.be.true;

// Contract: spm edit with user supplied local version of dependency
windowMock.showOpenDialog.resolves([testAssetUri("Swift-Markdown")]);
const id = "swift-markdown";
await useLocalDependency(id, workspaceContext);
await expect(executeTaskSpy.returnValues[4]).to.eventually.be.true;

// This will now pass as we have the required library
({ exitCode, output } = await executeTaskAndWaitForResult(tasks));
expect(exitCode).to.equal(0);
expect(output).to.include("defaultpackage");
expect(output).to.include("not used by any target");

// Contract: spm unedit
const updateWorkspaceSpy = sinon.spy(vscode.workspace, "updateWorkspaceFolders");
// We would love to call uneditDependency for coverage but there's no clean way to get
// a synchronize point for deterministic task completion so just call this function direct
await uneditFolderDependency(workspaceContext.currentFolder!, id, workspaceContext);
expect(updateWorkspaceSpy.calledOnce).to.be.true;

// Expect to fail again now dependency is missing
({ exitCode, output } = await executeTaskAndWaitForResult(tasks));
expect(exitCode).to.not.equal(0);
expect(output).to.include("PackageLib");
expect(output).to.include("required");
});
});
});

0 comments on commit 3b174b9

Please sign in to comment.