mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
355 lines
12 KiB
JavaScript
355 lines
12 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import crypto from 'node:crypto';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import test from 'node:test';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
|
const helperPath = path.resolve(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
'prepare-linux-runtime-source-snapshot.cjs'
|
|
);
|
|
|
|
async function loadHelper() {
|
|
if (!fs.existsSync(helperPath)) {
|
|
return null;
|
|
}
|
|
return import(pathToFileURL(helperPath).href);
|
|
}
|
|
|
|
function runGit(cwd, args, env = {}) {
|
|
const result = spawnSync('git', args, {
|
|
cwd,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
...env,
|
|
},
|
|
});
|
|
assert.equal(
|
|
result.status,
|
|
0,
|
|
`git ${args.join(' ')} failed:\n${result.stderr}`
|
|
);
|
|
return result.stdout.trim();
|
|
}
|
|
|
|
function createEquivalentCheckouts(root) {
|
|
const origin = path.join(root, 'origin');
|
|
fs.mkdirSync(origin);
|
|
runGit(origin, ['init', '--quiet']);
|
|
fs.mkdirSync(path.join(origin, 'src'));
|
|
fs.writeFileSync(path.join(origin, 'src', 'renderer.c'), 'int main() {}\n');
|
|
fs.writeFileSync(path.join(origin, 'LICENSE'), 'license\n');
|
|
fs.symlinkSync('../LICENSE', path.join(origin, 'src', 'license-link'));
|
|
runGit(origin, ['add', '.']);
|
|
runGit(origin, ['commit', '--quiet', '-m', 'source fixture'], {
|
|
GIT_AUTHOR_NAME: 'Fixture',
|
|
GIT_AUTHOR_EMAIL: 'fixture@example.invalid',
|
|
GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z',
|
|
GIT_COMMITTER_NAME: 'Fixture',
|
|
GIT_COMMITTER_EMAIL: 'fixture@example.invalid',
|
|
GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z',
|
|
});
|
|
const commit = runGit(origin, ['rev-parse', 'HEAD']);
|
|
const first = path.join(root, 'first');
|
|
const second = path.join(root, 'second');
|
|
runGit(root, ['clone', '--quiet', origin, first]);
|
|
runGit(root, ['clone', '--quiet', origin, second]);
|
|
|
|
fs.appendFileSync(
|
|
path.join(first, '.git', 'config'),
|
|
'\n[fixture]\n\tvalue = first\n'
|
|
);
|
|
fs.appendFileSync(
|
|
path.join(second, '.git', 'config'),
|
|
'\n[fixture]\n\tvalue = second\n'
|
|
);
|
|
fs.writeFileSync(path.join(first, '.git', 'fixture-cache'), 'one');
|
|
fs.writeFileSync(path.join(second, '.git', 'fixture-cache'), 'two');
|
|
const differentTime = new Date('2030-01-01T00:00:00Z');
|
|
fs.utimesSync(
|
|
path.join(second, '.git', 'index'),
|
|
differentTime,
|
|
differentTime
|
|
);
|
|
|
|
return { commit, first, second };
|
|
}
|
|
|
|
function createCheckoutWithSubmodule(root) {
|
|
const submoduleOrigin = path.join(root, 'submodule-origin');
|
|
fs.mkdirSync(submoduleOrigin);
|
|
runGit(submoduleOrigin, ['init', '--quiet']);
|
|
fs.writeFileSync(
|
|
path.join(submoduleOrigin, 'submodule-source.c'),
|
|
'int submodule_source;\n'
|
|
);
|
|
runGit(submoduleOrigin, ['add', '.']);
|
|
runGit(submoduleOrigin, ['commit', '--quiet', '-m', 'submodule source'], {
|
|
GIT_AUTHOR_NAME: 'Fixture',
|
|
GIT_AUTHOR_EMAIL: 'fixture@example.invalid',
|
|
GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z',
|
|
GIT_COMMITTER_NAME: 'Fixture',
|
|
GIT_COMMITTER_EMAIL: 'fixture@example.invalid',
|
|
GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z',
|
|
});
|
|
|
|
const checkout = path.join(root, 'checkout-with-submodule');
|
|
fs.mkdirSync(checkout);
|
|
runGit(checkout, ['init', '--quiet']);
|
|
fs.writeFileSync(path.join(checkout, 'README'), 'source tree\n');
|
|
runGit(checkout, ['add', 'README']);
|
|
runGit(checkout, [
|
|
'-c',
|
|
'protocol.file.allow=always',
|
|
'submodule',
|
|
'add',
|
|
'--quiet',
|
|
submoduleOrigin,
|
|
'3rdparty/example',
|
|
]);
|
|
runGit(checkout, ['commit', '--quiet', '-m', 'source with submodule'], {
|
|
GIT_AUTHOR_NAME: 'Fixture',
|
|
GIT_AUTHOR_EMAIL: 'fixture@example.invalid',
|
|
GIT_AUTHOR_DATE: '2000-01-02T00:00:00Z',
|
|
GIT_COMMITTER_NAME: 'Fixture',
|
|
GIT_COMMITTER_EMAIL: 'fixture@example.invalid',
|
|
GIT_COMMITTER_DATE: '2000-01-02T00:00:00Z',
|
|
});
|
|
return {
|
|
checkout,
|
|
expected: {
|
|
sourceGitCommit: runGit(checkout, ['rev-parse', 'HEAD']),
|
|
sourceSubmodules: runGit(checkout, [
|
|
'submodule',
|
|
'status',
|
|
'--recursive',
|
|
])
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.filter(Boolean),
|
|
},
|
|
};
|
|
}
|
|
|
|
function snapshotRecords(root) {
|
|
const records = [];
|
|
function visit(directory, relativeDirectory = '') {
|
|
for (const entry of fs
|
|
.readdirSync(directory, { withFileTypes: true })
|
|
.sort(({ name: left }, { name: right }) =>
|
|
left.localeCompare(right)
|
|
)) {
|
|
const relativePath = path.posix.join(relativeDirectory, entry.name);
|
|
const absolutePath = path.join(directory, entry.name);
|
|
const stat = fs.lstatSync(absolutePath);
|
|
if (entry.isDirectory()) {
|
|
records.push({
|
|
path: `${relativePath}/`,
|
|
mode: stat.mode & 0o777,
|
|
});
|
|
visit(absolutePath, relativePath);
|
|
} else if (entry.isSymbolicLink()) {
|
|
records.push({
|
|
path: relativePath,
|
|
mode: stat.mode & 0o777,
|
|
symlink: fs.readlinkSync(absolutePath),
|
|
});
|
|
} else {
|
|
records.push({
|
|
path: relativePath,
|
|
mode: stat.mode & 0o777,
|
|
sha256: crypto
|
|
.createHash('sha256')
|
|
.update(fs.readFileSync(absolutePath))
|
|
.digest('hex'),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
visit(root);
|
|
return records;
|
|
}
|
|
|
|
test('prepares identical VCS-free snapshots from equivalent clean checkouts', async (t) => {
|
|
const helper = await loadHelper();
|
|
assert.ok(helper, 'the deterministic source snapshot helper must exist');
|
|
const root = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'iptvnator-source-snapshot-')
|
|
);
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const { commit, first, second } = createEquivalentCheckouts(root);
|
|
const firstOutput = path.join(root, 'snapshot-first');
|
|
const secondOutput = path.join(root, 'snapshot-second');
|
|
const expected = {
|
|
sourceGitCommit: commit,
|
|
sourceSubmodules: [],
|
|
};
|
|
|
|
const firstRecord = helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: first,
|
|
outputPath: firstOutput,
|
|
expected,
|
|
});
|
|
const secondRecord = helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: second,
|
|
outputPath: secondOutput,
|
|
expected,
|
|
});
|
|
|
|
assert.deepEqual(firstRecord, expected);
|
|
assert.deepEqual(secondRecord, expected);
|
|
assert.deepEqual(
|
|
snapshotRecords(firstOutput),
|
|
snapshotRecords(secondOutput)
|
|
);
|
|
assert.equal(
|
|
snapshotRecords(firstOutput).some(({ path: recordPath }) =>
|
|
recordPath.split('/').includes('.git')
|
|
),
|
|
false
|
|
);
|
|
assert.equal(
|
|
fs.readlinkSync(path.join(firstOutput, 'src', 'license-link')),
|
|
'../LICENSE'
|
|
);
|
|
});
|
|
|
|
test('validates checkout cleanliness and exact commit/submodule identities before copying', async (t) => {
|
|
const helper = await loadHelper();
|
|
assert.ok(helper, 'the deterministic source snapshot helper must exist');
|
|
const root = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'iptvnator-source-identity-')
|
|
);
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const { commit, first, second } = createEquivalentCheckouts(root);
|
|
|
|
assert.throws(
|
|
() =>
|
|
helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: first,
|
|
outputPath: path.join(root, 'wrong-commit'),
|
|
expected: {
|
|
sourceGitCommit: '0'.repeat(40),
|
|
sourceSubmodules: [],
|
|
},
|
|
}),
|
|
/commit does not match/
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: first,
|
|
outputPath: path.join(root, 'wrong-submodules'),
|
|
expected: {
|
|
sourceGitCommit: commit,
|
|
sourceSubmodules: [`${'1'.repeat(40)} 3rdparty/example`],
|
|
},
|
|
}),
|
|
/submodules do not match/
|
|
);
|
|
|
|
fs.writeFileSync(path.join(second, 'untracked-build-output'), 'dirty');
|
|
assert.throws(
|
|
() =>
|
|
helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: second,
|
|
outputPath: path.join(root, 'dirty'),
|
|
expected: {
|
|
sourceGitCommit: commit,
|
|
sourceSubmodules: [],
|
|
},
|
|
}),
|
|
/dirty or untracked files/
|
|
);
|
|
|
|
const existingOutput = path.join(root, 'existing-output');
|
|
fs.mkdirSync(existingOutput);
|
|
fs.writeFileSync(
|
|
path.join(existingOutput, 'owned-by-someone-else'),
|
|
'keep'
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: first,
|
|
outputPath: existingOutput,
|
|
expected: {
|
|
sourceGitCommit: commit,
|
|
sourceSubmodules: [],
|
|
},
|
|
}),
|
|
/output.*must not already exist/i
|
|
);
|
|
assert.equal(
|
|
fs.readFileSync(
|
|
path.join(existingOutput, 'owned-by-someone-else'),
|
|
'utf8'
|
|
),
|
|
'keep'
|
|
);
|
|
|
|
const existingSymlink = path.join(root, 'existing-symlink');
|
|
fs.symlinkSync('missing-target', existingSymlink);
|
|
assert.throws(
|
|
() =>
|
|
helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: first,
|
|
outputPath: existingSymlink,
|
|
expected: {
|
|
sourceGitCommit: commit,
|
|
sourceSubmodules: [],
|
|
},
|
|
}),
|
|
/output.*must not already exist/i
|
|
);
|
|
assert.equal(fs.readlinkSync(existingSymlink), 'missing-target');
|
|
});
|
|
|
|
test('preserves recursive submodule sources while stripping every nested .git link', async (t) => {
|
|
const helper = await loadHelper();
|
|
assert.ok(helper, 'the deterministic source snapshot helper must exist');
|
|
const root = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'iptvnator-source-submodule-')
|
|
);
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const { checkout, expected } = createCheckoutWithSubmodule(root);
|
|
const output = path.join(root, 'submodule-snapshot');
|
|
|
|
assert.deepEqual(
|
|
helper.prepareLinuxRuntimeSourceSnapshot({
|
|
checkoutPath: checkout,
|
|
outputPath: output,
|
|
expected,
|
|
}),
|
|
expected
|
|
);
|
|
assert.equal(
|
|
fs.readFileSync(
|
|
path.join(output, '3rdparty', 'example', 'submodule-source.c'),
|
|
'utf8'
|
|
),
|
|
'int submodule_source;\n'
|
|
);
|
|
assert.doesNotThrow(() => helper.assertNoGitMetadata(output));
|
|
});
|
|
|
|
test('fails closed when a prepared snapshot contains any nested .git entry', async (t) => {
|
|
const helper = await loadHelper();
|
|
assert.ok(helper, 'the deterministic source snapshot helper must exist');
|
|
const root = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'iptvnator-source-vcs-entry-')
|
|
);
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
fs.mkdirSync(path.join(root, 'nested', '.git'), { recursive: true });
|
|
fs.writeFileSync(path.join(root, 'nested', '.git', 'index'), 'metadata');
|
|
|
|
assert.throws(
|
|
() => helper.assertNoGitMetadata(root),
|
|
/must not contain VCS metadata.*nested\/\.git/i
|
|
);
|
|
});
|