|
2 | 2 | * Copyright (c) Microsoft Corporation. All rights reserved. |
3 | 3 | *--------------------------------------------------------------------------------------------*/ |
4 | 4 |
|
| 5 | +import * as assert from 'assert'; |
5 | 6 | import * as fs from 'fs/promises'; |
6 | 7 | import * as path from 'path'; |
7 | 8 |
|
8 | | -import { devContainerDown, devContainerUp, shellExec } from './testUtils'; |
| 9 | +import { getCLIHost, loadNativeModule } from '../spec-common/commonUtils'; |
| 10 | +import { preprocessDockerExtensionFile } from '../spec-node/dockerfilePreprocessor'; |
| 11 | +import { nullLog } from '../spec-utils/log'; |
9 | 12 |
|
10 | | -const pkg = require('../../package.json'); |
| 13 | +(process.platform === 'win32' ? describe : describe.skip)('dockerfilePreprocessor unit (Windows)', function () { |
| 14 | + const fixtureFolder = path.join(__dirname, 'configs', 'dockerfile-clang-preprocessor'); |
| 15 | + const inputPath = path.join(fixtureFolder, 'Dockerfile.in'); |
| 16 | + const outputPath = path.join(fixtureFolder, '.devcontainer-preprocessed', 'Dockerfile'); |
| 17 | + const generatedRelPath = path.join('build', 'Dockerfile.generated'); |
| 18 | + const generatedAbsPath = path.join(fixtureFolder, generatedRelPath); |
| 19 | + const expectedSingleFileDockerfile = [ |
| 20 | + 'FROM mcr.microsoft.com/windows/nanoserver:ltsc2022', |
| 21 | + 'SHELL ["powershell", "-NoLogo", "-NoProfile", "-Command"]', |
| 22 | + 'RUN Write-Host "Installing Node.js"', |
| 23 | + 'RUN Write-Host "Installing Python"', |
| 24 | + 'RUN Write-Host "Installing curl and wget"', |
| 25 | + 'ENV APP_ENV=development', |
| 26 | + 'ENV APP_DEBUG=true', |
| 27 | + 'RUN Write-Host "Installing vim"', |
| 28 | + 'COPY ./test.ps1 C:/usr/local/bin/test.ps1', |
| 29 | + '', |
| 30 | + ].join('\n'); |
11 | 31 |
|
12 | | -(process.platform === 'win32' ? describe : describe.skip)('dockerfilePreprocessor integration (Windows)', function () { |
13 | | - this.timeout('240s'); |
| 32 | + const cleanupGeneratedArtifacts = async () => { |
| 33 | + await Promise.all([ |
| 34 | + fs.rm(outputPath, { recursive: true, force: true }), |
| 35 | + fs.rm(generatedAbsPath, { recursive: true, force: true }), |
| 36 | + ]); |
| 37 | + }; |
14 | 38 |
|
15 | | - const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); |
16 | | - const cli = `npx --prefix ${tmp} devcontainer`; |
17 | | - const generatedArtifacts = ['Dockerfile', '.devcontainer-lock.json', '.devcontainer-preprocessed']; |
18 | | - let clangAvailable = false; |
| 39 | + beforeEach(async () => { |
| 40 | + await cleanupGeneratedArtifacts(); |
| 41 | + }); |
19 | 42 |
|
20 | | - const cleanupGeneratedArtifacts = async (testFolder: string) => { |
21 | | - await Promise.all(generatedArtifacts.map(relative => fs.rm(path.join(testFolder, relative), { recursive: true, force: true }))); |
22 | | - }; |
| 43 | + afterEach(async () => { |
| 44 | + await cleanupGeneratedArtifacts(); |
| 45 | + }); |
23 | 46 |
|
24 | | - before('Install', async () => { |
25 | | - await fs.rm(path.join(__dirname, 'tmp', 'node_modules'), { recursive: true, force: true }); |
26 | | - await fs.mkdir(path.join(__dirname, 'tmp'), { recursive: true }); |
27 | | - await shellExec(`npm --prefix ${tmp} install devcontainers-cli-${pkg.version}.tgz`); |
28 | | - const clangCheck = await shellExec('where clang', undefined, true, true); |
29 | | - clangAvailable = !clangCheck.error && Boolean(clangCheck.stdout.trim()); |
| 47 | + |
| 48 | + it('runs preprocessor in single-file mode and writes CLI-owned output', async () => { |
| 49 | + const cliHost = await getCLIHost(process.cwd(), loadNativeModule, true); |
| 50 | + const result = await preprocessDockerExtensionFile( |
| 51 | + { cliHost, output: nullLog }, |
| 52 | + { |
| 53 | + dockerfilePreprocessor: { |
| 54 | + tool: process.execPath, |
| 55 | + args: ['-e', "const fs=require('fs');const path=require('path');const [input,output]=process.argv.slice(1);if(!input||!output){process.exit(1);}const root=path.dirname(input);const source=fs.readFileSync(input,'utf8');if(!source.includes('#define BASE_IMAGE mcr.microsoft.com/windows/nanoserver:ltsc2022')){process.exit(1);}const common=fs.readFileSync(path.join(root,'common.Dockerfile'),'utf8').trim();const tools=fs.readFileSync(path.join(root,'tools.Dockerfile'),'utf8').trim();const lines=['FROM mcr.microsoft.com/windows/nanoserver:ltsc2022','SHELL [\"powershell\", \"-NoLogo\", \"-NoProfile\", \"-Command\"]','RUN Write-Host \"Installing Node.js\"','RUN Write-Host \"Installing Python\"',common,tools,''];fs.mkdirSync(path.dirname(output),{recursive:true});fs.writeFileSync(output,lines.join('\\n'));"], |
| 56 | + outputMode: 'single-file', |
| 57 | + }, |
| 58 | + }, |
| 59 | + inputPath |
| 60 | + ); |
| 61 | + |
| 62 | + assert.strictEqual(result, outputPath); |
| 63 | + assert.strictEqual(await fs.readFile(outputPath, 'utf8'), expectedSingleFileDockerfile); |
30 | 64 | }); |
31 | 65 |
|
32 | | - it('should preprocess a Dockerfile.in during up clang on Windows', async function () { |
33 | | - if (!clangAvailable) { |
34 | | - this.skip(); |
35 | | - } |
36 | | - const testFolder = `${__dirname}/configs/dockerfile-clang-preprocessor`; |
37 | | - await cleanupGeneratedArtifacts(testFolder); |
38 | | - let containerId: string | undefined; |
39 | | - try { |
40 | | - containerId = (await devContainerUp(cli, testFolder)).containerId; |
41 | | - await shellExec(`${cli} exec --workspace-folder ${testFolder} sh -lc 'command -v nodejs && command -v python3 && command -v curl && command -v wget && command -v vim && test -f /usr/local/bin/test.sh'`); |
42 | | - } finally { |
43 | | - await devContainerDown({ containerId, doNotThrow: true }); |
44 | | - await cleanupGeneratedArtifacts(testFolder); |
45 | | - } |
| 66 | + it('promotes generatedDockerfile output into CLI-owned output path', async () => { |
| 67 | + const cliHost = await getCLIHost(process.cwd(), loadNativeModule, true); |
| 68 | + const result = await preprocessDockerExtensionFile( |
| 69 | + { cliHost, output: nullLog }, |
| 70 | + { |
| 71 | + dockerfilePreprocessor: { |
| 72 | + tool: process.execPath, |
| 73 | + args: ['-e', "const fs=require('fs');const path=require('path');const out=process.env.generated_dockerfile;if(!out){process.exit(1);}fs.mkdirSync(path.dirname(out),{recursive:true});fs.writeFileSync(out,'FROM mcr.microsoft.com/windows/nanoserver:ltsc2022\\n');"], |
| 74 | + generatedDockerfile: generatedRelPath, |
| 75 | + }, |
| 76 | + }, |
| 77 | + inputPath |
| 78 | + ); |
| 79 | + |
| 80 | + assert.strictEqual(result, outputPath); |
| 81 | + assert.strictEqual(await fs.readFile(outputPath, 'utf8'), 'FROM mcr.microsoft.com/windows/nanoserver:ltsc2022\n'); |
| 82 | + assert.strictEqual(await cliHost.isFile(generatedAbsPath), false); |
46 | 83 | }); |
47 | 84 | }); |
0 commit comments