Skip to content

Commit d1cbe57

Browse files
committed
add tests
1 parent 346afd5 commit d1cbe57

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

src/test/preprocessPodman.test.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { assert } from 'chai';
7+
import * as fs from 'fs';
8+
import * as os from 'os';
9+
import * as path from 'path';
10+
import { ExecFunction, plainExec } from '../spec-common/commonUtils';
11+
import { nullLog } from '../spec-utils/log';
12+
import { ensureDockerfileHasFinalStageName, preprocessDockerfileIn } from '../spec-node/dockerfileUtils';
13+
14+
describe('preprocessDockerfileIn', () => {
15+
// Use the actual Dockerfile.in from the podman-with-cpp config directory.
16+
// It defines BASE_IMAGE, INSTALL_NODE, and INSTALL_PYTHON macros, uses
17+
// #ifdef/#endif blocks, and #includes common.Dockerfile and tools.Dockerfile.
18+
const configDir = path.join(__dirname, 'configs', 'podman-with-cpp');
19+
const dockerfileInPath = path.join(configDir, 'Dockerfile.in');
20+
21+
let tmpDir: string;
22+
23+
before(() => {
24+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'devcontainer-preprocess-test-'));
25+
});
26+
27+
after(() => {
28+
fs.rmSync(tmpDir, { recursive: true, force: true });
29+
});
30+
31+
describe('when cpp is available on the host', () => {
32+
let exec: ExecFunction;
33+
34+
before(async () => {
35+
exec = await plainExec(undefined);
36+
});
37+
38+
it('should preprocess #ifdef / #endif conditional blocks', async () => {
39+
// INSTALL_NODE and INSTALL_PYTHON are both #define'd in Dockerfile.in
40+
const result = await preprocessDockerfileIn(dockerfileInPath, exec, nullLog);
41+
42+
assert.include(result, 'apt-get install -y nodejs');
43+
assert.include(result, 'apt-get install -y python3');
44+
assert.notInclude(result, '#ifdef');
45+
assert.notInclude(result, '#endif');
46+
});
47+
48+
it('should inline content from #include directives', async () => {
49+
// Dockerfile.in #includes common.Dockerfile and tools.Dockerfile
50+
const result = await preprocessDockerfileIn(dockerfileInPath, exec, nullLog);
51+
52+
assert.include(result, 'apt-get install -y curl wget');
53+
assert.include(result, 'APP_ENV=development');
54+
assert.include(result, 'apt-get install -y vim');
55+
});
56+
57+
it('should substitute macros and pass through plain Dockerfile content', async () => {
58+
// BASE_IMAGE is #define'd as ubuntu:20.04 in Dockerfile.in
59+
const result = await preprocessDockerfileIn(dockerfileInPath, exec, nullLog);
60+
61+
assert.include(result, 'FROM ubuntu:20.04');
62+
assert.notInclude(result, 'FROM BASE_IMAGE');
63+
});
64+
65+
it('should produce output parseable by ensureDockerfileHasFinalStageName when stage is unnamed', async () => {
66+
// Dockerfile.in has no AS-named final stage, so a fallback name is assigned
67+
const preprocessed = await preprocessDockerfileIn(dockerfileInPath, exec, nullLog);
68+
const { lastStageName, modifiedDockerfile } = ensureDockerfileHasFinalStageName(preprocessed, 'dev');
69+
70+
assert.equal(lastStageName, 'dev');
71+
assert.isDefined(modifiedDockerfile);
72+
});
73+
74+
it('should produce output where ensureDockerfileHasFinalStageName assigns a name to an unnamed final stage', async () => {
75+
// Dockerfile.in has no AS-named final stage, so the auto-generated label is injected
76+
const preprocessed = await preprocessDockerfileIn(dockerfileInPath, exec, nullLog);
77+
const { lastStageName, modifiedDockerfile } = ensureDockerfileHasFinalStageName(preprocessed, 'dev_container_auto_added_stage_label');
78+
79+
assert.equal(lastStageName, 'dev_container_auto_added_stage_label');
80+
assert.isDefined(modifiedDockerfile);
81+
assert.include(modifiedDockerfile!, 'AS dev_container_auto_added_stage_label');
82+
});
83+
84+
it('should throw an error when the input file does not exist', async () => {
85+
const nonExistentPath = path.join(tmpDir, 'does-not-exist.in');
86+
87+
let caughtError: Error | undefined;
88+
try {
89+
await preprocessDockerfileIn(nonExistentPath, exec, nullLog);
90+
} catch (err: any) {
91+
caughtError = err;
92+
}
93+
94+
assert.isDefined(caughtError);
95+
assert.include(caughtError!.message, 'Failed to preprocess');
96+
});
97+
});
98+
99+
describe('when cpp is not available on the host', () => {
100+
// Simulate ENOENT by making exec throw with code 'ENOENT',
101+
// mirroring what happens when the binary is missing.
102+
const cppNotFoundExec: ExecFunction = async (_params) => {
103+
const err: any = new Error('spawn cpp ENOENT');
104+
err.code = 'ENOENT';
105+
throw err;
106+
};
107+
108+
it('should throw a clear error message directing the user to install cpp', async () => {
109+
let caughtError: Error | undefined;
110+
try {
111+
await preprocessDockerfileIn(dockerfileInPath, cppNotFoundExec, nullLog);
112+
} catch (err: any) {
113+
caughtError = err;
114+
}
115+
116+
assert.isDefined(caughtError);
117+
assert.include(caughtError!.message, 'cpp');
118+
});
119+
});
120+
});

0 commit comments

Comments
 (0)