Skip to content
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

allow referencing external Features in devcontainer features test command #220

Merged
merged 3 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/spec-configuration/containerFeaturesConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,8 @@ export async function processFeatureIdentifier(output: Log, configPath: string,
// We expect all GitHub/registry features to follow the triple slash pattern at this point
// eg: <publisher>/<feature-set>/<feature>
if (splitOnSlash.length !== 3 || splitOnSlash.some(x => x === '') || !allowedFeatureIdRegex.test(splitOnSlash[2])) {
output.write(`Invalid parse for GitHub Release feature: Follow format '<publisher>/<feature-set>/<feature>, or republish feature to OCI registry.'`, LogLevel.Error);
// This is the final fallback. If we end up here, we weren't able to resolve the Feature
output.write(`Could not resolve Feature '${userFeature.id}'. Ensure the Feature is published and accessible from your current environment.`, LogLevel.Error);
return undefined;
}
const owner = splitOnSlash[0];
Expand Down
10 changes: 9 additions & 1 deletion src/spec-node/featuresCLI/testCommandImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,13 +330,21 @@ async function generateProjectFromScenario(

let features = scenarioObject.features;
if (!scenarioObject || !features) {
fail(`Scenario '${scenarioId}' is missing features!`);
fail(`Scenario '${scenarioId}' is missing Features!`);
return ''; // Exits in the 'fail()' before this line is reached.
}

// Prefix the local path to the collections directory
let updatedFeatures: Record<string, string | boolean | Record<string, string | boolean>> = {};
for (const [featureId, featureValue] of Object.entries(features)) {
// Do not overwrite Features that are not part of the target collection
// The '/' is only valid in a fully qualified Feature ID (eg: '[ghcr].io/devcontainers/features/go')
// This lets you use external Features as a part of the test scenario.
if (featureId.indexOf('/') !== -1) {
updatedFeatures[featureId] = featureValue;
continue;
}

// Copy the feature source code to the temp folder
const pathToFeatureSource = `${collectionsDirectory}/src/${featureId}`;
await cpDirectoryLocal(pathToFeatureSource, `${tmpFolder}/.devcontainer/${featureId}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"id": "a",
"version": "1.0.0",
"installsAfter": [
"./b"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
set -e

echo "Activating feature 'a'"

touch /usr/local/bin/a

# InstallsAfter Feature B

if [ ! -f /usr/local/bin/b ]; then
echo "Feature B not available!"
exit 1
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "b",
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
set -e

echo "Activating feature 'b'"

touch /usr/local/bin/b

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

test -f /usr/local/bin/a
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

test -f /usr/local/bin/b
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "a",
"version": "1.0.0",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
set -e

echo "Activating feature 'a'"

touch /usr/local/bin/a

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"id": "b",
"version": "1.0.0",
"installsAfter": [
"./a"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh
set -e

echo "Activating feature 'b'"

touch /usr/local/bin/b

# InstallsAfter Feature A
if [ ! -f /usr/local/bin/a ]; then
echo "Feature A not available!"
exit 1
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

test -f /usr/local/bin/a
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

test -f /usr/local/bin/b
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,16 @@
"favorite": "Magenta"
}
}
},
"with_external_feature": {
"image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu",
"features": {
"hello": {
"greeting": "How ya doing"
},
"ghcr.io/devcontainers/feature-template/color:1": {
"favorite": "Silver"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

# Definition specific tests
check "correct color" color | grep "Silver"
check "correct greeting" hello | grep "How ya doing"

# Report result
reportResults
52 changes: 50 additions & 2 deletions src/test/container-features/featuresCLICommands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ describe('CLI features subcommands', async function () {
✅ Passed: 'color'
✅ Passed: 'specific_color_scenario'
✅ Passed: 'hello'
✅ Passed: 'custom_options'`;
✅ Passed: 'custom_options'
✅ Passed: 'with_external_feature'`;
const hasExpectedTestReport = result.stdout.includes(expectedTestReport);
assert.isTrue(hasExpectedTestReport);

Expand Down Expand Up @@ -95,7 +96,8 @@ describe('CLI features subcommands', async function () {
assert.isDefined(result);

const expectedTestReport = ` ================== TEST REPORT ==================
✅ Passed: 'custom_options'`;
✅ Passed: 'custom_options'
✅ Passed: 'with_external_feature'`;
const hasExpectedTestReport = result.stdout.includes(expectedTestReport);
assert.isTrue(hasExpectedTestReport);

Expand Down Expand Up @@ -124,6 +126,52 @@ describe('CLI features subcommands', async function () {
assert.isDefined(result.error);
});

// Feature A will crash in its install.sh if B has not already run.
it('features test subcommand installsAfter B -> A', async function () {
const collectionFolder = `${__dirname}/example-v2-features-sets/a-installs-after-b`;
let success = false;
let result: ExecResult | undefined = undefined;
try {
result = await shellExec(`${cli} features test --log-level trace ${collectionFolder}`);
success = true;

} catch (error) {
assert.fail('features test sub-command should not throw');
}

assert.isTrue(success);
assert.isDefined(result);

const expectedTestReport = ` ================== TEST REPORT ==================
✅ Passed: 'a'
✅ Passed: 'b'`;
const hasExpectedTestReport = result.stdout.includes(expectedTestReport);
assert.isTrue(hasExpectedTestReport);
});

// Feature B will crash in its install.sh if A has not already run.
it('features test subcommand installsAfter A -> B', async function () {
const collectionFolder = `${__dirname}/example-v2-features-sets/b-installs-after-a`;
let success = false;
let result: ExecResult | undefined = undefined;
try {
result = await shellExec(`${cli} features test --log-level trace ${collectionFolder}`);
success = true;

} catch (error) {
assert.fail('features test sub-command should not throw');
}

assert.isTrue(success);
assert.isDefined(result);

const expectedTestReport = ` ================== TEST REPORT ==================
✅ Passed: 'a'
✅ Passed: 'b'`;
const hasExpectedTestReport = result.stdout.includes(expectedTestReport);
assert.isTrue(hasExpectedTestReport);
});

// -- Packaging

it('features package subcommand by collection', async function () {
Expand Down