Skip to content

Commit 27814fd

Browse files
committed
maker-appimage: Rework tests.
• Restructure tests, grouping common tests in suites by dependence on the same environment. • Add tests for checking icon generation and placement in AppImage. Note that it depends on fuse mounting and executable tmpdir (same as mock AppImage execution).
1 parent b54602e commit 27814fd

3 files changed

Lines changed: 104 additions & 45 deletions

File tree

makers/appimage/test/make.ts

Lines changed: 103 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import {
1717
access,
1818
chmod,
1919
constants,
20+
lstat,
2021
mkdtemp,
2122
open,
2223
readdir,
24+
readlink,
2325
rm,
2426
writeFile
2527
} from "node:fs/promises";
@@ -35,9 +37,20 @@ import assert from "node:assert";
3537
import MakerAppImage from "@reforged/maker-appimage";
3638

3739
import type { ForgeArch } from "@reforged/maker-appimage";
40+
import type { IconSet } from "@reforged/maker-types";
41+
42+
const icon: IconSet = {};
43+
44+
const icons = Object.freeze([
45+
["scalable","svg"],
46+
["1024x1024","png"]
47+
] as const);
48+
49+
for(const str of icons)
50+
icon[str[0]] = resolve(import.meta.dirname,`res/empty_${str[0]}.${str[1]}`);
3851

3952
/** Maker to test. */
40-
const maker = new MakerAppImage();
53+
const maker = new MakerAppImage({ options: { icon } });
4154
await maker.prepareConfig(process.arch);
4255

4356
// Mock app metadata
@@ -88,26 +101,18 @@ const skip = maker.isSupportedOnCurrentPlatform() ?
88101
`One or more binaries are missing: ${maker.requiredExternalBinaries.join(', ')}` :
89102
`Unsupported platform: ${process.platform}-${process.arch}`;
90103

91-
suites.push(describe("MakerAppimage is working correctly", {skip}, () => {
92-
let AResolve: (arg0: string) => any, AReject: (reason?: unknown) => void;
93-
/** Resolved output of successful make, to re-use it in another tests. */
94-
const AppImageDir:Promise<string> = new Promise(
95-
(resolve,reject) => { AResolve = resolve, AReject = reject }
96-
);
97-
98-
it("creates valid AppImage binary", async() => {
99-
maker.make({
104+
suites.push(describe("MakerAppimage is working correctly", {skip}, async() => {
105+
await it("successfully creates AppImage binary in predictable path", async(ctx) => {
106+
const AppImageDir = maker.make({
100107
packageJSON,
101108
forgeConfig,
102109
appName: packageJSON.productName,
103110
dir: await mockAppPath,
104111
makeDir: await mockMkPath,
105112
targetArch: process.arch as ForgeArch,
106113
targetPlatform: process.platform
107-
}).then(dir => AResolve(dir[0]),reason => AReject(reason));
108-
114+
}).then(([path]) => path);
109115
await assert.doesNotReject(AppImageDir);
110-
111116
assert.strictEqual(
112117
await AppImageDir,
113118
resolve(
@@ -117,45 +122,98 @@ suites.push(describe("MakerAppimage is working correctly", {skip}, () => {
117122
`${packageJSON.productName}-${packageJSON.version}-${process.arch}.AppImage`
118123
)
119124
)
120-
});
121-
122-
it("cleans-up workDir after completion", async () => {
123-
await AppImageDir;
125+
ctx.test("that is an ELF file", async() => {
126+
const fd = await open(await AppImageDir);
127+
const buff = new Uint8Array(8);
128+
await fd.read(buff,0,8);
129+
assert.strictEqual(
130+
[...buff].map(v=>v.toString(16)).join(' '),
131+
// ELF magic HEX
132+
"7f 45 4c 46 2 1 1 0"
133+
)
134+
fd.close();
135+
})
136+
ctx.test("that is runnable and working fine", async ctx => {
137+
const exec = promisify(execFile);
138+
const AppImage = await AppImageDir
139+
// Skip this test for non-exec tmpdir.
140+
if(await access(AppImage,constants.X_OK).then(_=>false,_=>true)) {
141+
await chmod(AppImage,0o755);
142+
return access(AppImage,constants.X_OK).then(
143+
()=>Promise.reject("Maker failed to set exec permissions"),
144+
()=>ctx.skip("Non-executable tmpdir.")
145+
);
146+
}
147+
const cp = exec(AppImage)
148+
await assert.doesNotReject(cp);
149+
assert.strictEqual((await cp).stdout,"Hello world!\n")
150+
})
151+
ctx.test("that contains valid icon hierarchy", async ctx => {
152+
const AppImage = await AppImageDir
153+
// Skip this test for non-exec tmpdir (due to appimage mounting).
154+
if(await access(AppImage,constants.X_OK).then(_=>false,_=>true)) {
155+
await chmod(AppImage,0o755);
156+
return access(AppImage,constants.X_OK).then(
157+
()=>Promise.reject("Maker failed to set exec permissions"),
158+
()=>ctx.skip("Non-executable tmpdir.")
159+
);
160+
}
161+
const cp = execFile(AppImage, ["--appimage-mount"])
162+
// Wait for mountpoint.
163+
const mount = await new Promise<string>((resolve) => {
164+
let data = "";
165+
cp.stdout?.once("data", (chunk:string) => {
166+
data+=String(chunk);
167+
if(data.includes("\n")) resolve(data.trimEnd())
168+
})
169+
cp.stdout?.once("end", () => resolve(data))
170+
cp.stdout?.once("close",() => resolve(data));
171+
})
172+
// Do FS checks
173+
assert.ok(mount);
174+
const test = [];
175+
test.push(ctx.test("with top-level symlinks", async () => {
176+
const promises: Promise<unknown>[] = [];
177+
for(const path of [".DirIcon",`${packageJSON.name}.svg`]) {
178+
const file = resolve(mount,path);
179+
promises.push(Promise.all([lstat(file),readlink(file)]).then(([stats,link]) => {
180+
assert.ok(stats.isSymbolicLink(),`${path} is not symlink`);
181+
// Check if default icon is scalable
182+
assert.strictEqual(
183+
link,
184+
`usr/share/icons/hicolor/scalable/apps/${packageJSON.name}.svg`
185+
);
186+
}));
187+
}
188+
await Promise.all(promises);
189+
}))
190+
test.push(ctx.test("with icons in 'usr/share/icons'", async () => {
191+
const promises: Promise<unknown>[] = [];
192+
for(const path of icons)
193+
promises.push(assert.doesNotReject(access(
194+
resolve(mount,"usr/share/icons/hicolor",path[0],"apps",`${packageJSON.name}.${path[1]}`),
195+
constants.R_OK
196+
)));
197+
await Promise.all(promises);
198+
}))
199+
try {
200+
assert.ok(mount);
201+
await Promise.all(test);
202+
} catch(err) {
203+
cp.kill();
204+
throw err;
205+
}
206+
cp.kill();
207+
})
208+
})
209+
it("cleans-up working directory after completion", async () => {
124210
assert.ok(
125211
// We know workDir is somewhere in tmpdir, but not really its exact path.
126212
// Hence fail for all possible workDir matches.
127213
!(await readdir(tmpdir()))
128214
.some(dir => dir.startsWith(`.${packageJSON.productName}-${packageJSON.version}-${process.arch}-`))
129215
)
130216
})
131-
132-
it("outputs AppImage that is an ELF file", async() => {
133-
const fd = await open(await AppImageDir);
134-
const buff = new Uint8Array(8);
135-
await fd.read(buff,0,8);
136-
assert.strictEqual(
137-
[...buff].map(v=>v.toString(16)).join(' '),
138-
// ELF magic HEX
139-
"7f 45 4c 46 2 1 1 0"
140-
)
141-
fd.close();
142-
})
143-
144-
it("outputs AppImage that is runnable and working fine", async ctx => {
145-
const exec = promisify(execFile);
146-
const AppImage = await AppImageDir
147-
// Skip this test for non-exec tmpdir.
148-
if(await access(AppImage,constants.X_OK).then(_=>false,_=>true)) {
149-
await chmod(AppImage,0o755);
150-
return access(AppImage,constants.X_OK).then(
151-
()=>Promise.reject("Maker failed to set exec permissions"),
152-
()=>ctx.skip("Non-executable tmpdir.")
153-
);
154-
}
155-
const cp = exec(AppImage)
156-
await assert.doesNotReject(cp);
157-
assert.strictEqual((await cp).stdout,"Hello world!\n")
158-
})
159217
}));
160218

161219
suites.push(describe("MakerAppImage fails for invalid cases", {skip}, () => {
308 Bytes
Loading
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)