Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/rstack/rstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { define } from 'rstack';

define.test(() => {
return {
pool: 'vmForks',
// Temporary projects and fixture projects may contain files that match
// Rstest's test glob.
exclude: [
Expand Down
70 changes: 36 additions & 34 deletions packages/rstack/tests/config/extends/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import { expect, test } from 'rstack/test';
import { loadRstackConfig } from '../../../src/config.ts';
import { resolveConfigLayers } from '../../../src/configLayers.ts';

const cwd = import.meta.dirname;
const params = { command: 'build', env: 'production' } as const;

test('loads all seven tools from shared configs and tracks their dependencies', async () => {
const { configs, filePath, dependencies } = await loadRstackConfig({ cwd });
const layers = [configs];
test('loads all seven tools from shared configs and tracks their dependencies', () => {
// Dependency tracking uses Node loader hooks, outside the test VM.
const output = execFileSync(
process.execPath,
[path.join(cwd, 'load-shared.ts')],
{ encoding: 'utf8', timeout: 10_000 },
);
const result: unknown = JSON.parse(output);
const dependencies: unknown = expect.arrayContaining([
path.join(cwd, 'shared.ts'),
]);

expect({
app: await resolveConfigLayers(layers, 'app', params),
lib: await resolveConfigLayers(layers, 'lib', params),
doc: await resolveConfigLayers(layers, 'doc'),
test: await resolveConfigLayers(layers, 'test'),
lint: await resolveConfigLayers(layers, 'lint'),
fmt: await resolveConfigLayers(layers, 'fmt'),
staged: await resolveConfigLayers(layers, 'staged'),
}).toEqual({
app: [
{
source: {
define: { ENV: 'production' },
expect(result).toEqual({
configs: {
app: [
{
source: {
define: { ENV: 'production' },
},
},
},
],
lib: [
{
lib: [{ format: 'esm' }],
},
],
doc: [{ root: 'docs' }],
test: [{ setupFiles: [path.join(cwd, 'setup.ts')] }],
lint: [
[
],
lib: [
{
rules: { 'no-debugger': 'error' },
lib: [{ format: 'esm' }],
},
],
],
fmt: [{ semi: false, singleQuote: false, printWidth: 80 }],
staged: [{ '*.ts': 'rs lint' }],
doc: [{ root: 'docs' }],
test: [{ setupFiles: [path.join(cwd, 'setup.ts')] }],
lint: [
[
{
rules: { 'no-debugger': 'error' },
},
],
],
fmt: [{ semi: false, singleQuote: false, printWidth: 80 }],
staged: [{ '*.ts': 'rs lint' }],
},
filePath: path.join(cwd, 'rstack.config.ts'),
dependencies,
});
expect(filePath).toBe(path.join(cwd, 'rstack.config.ts'));
expect(dependencies).toContain(path.join(cwd, 'shared.ts'));
});

test('preserves project priority regardless of call order and across reloads', async () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/rstack/tests/config/extends/load-shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { loadRstackConfig } from '../../../src/config.ts';
import { resolveConfigLayers } from '../../../src/configLayers.ts';

const { configs, filePath, dependencies } = await loadRstackConfig({
cwd: import.meta.dirname,
});
const layers = [configs];
const params = { command: 'build', env: 'production' } as const;

console.log(
JSON.stringify({
configs: {
app: await resolveConfigLayers(layers, 'app', params),
lib: await resolveConfigLayers(layers, 'lib', params),
doc: await resolveConfigLayers(layers, 'doc'),
test: await resolveConfigLayers(layers, 'test'),
lint: await resolveConfigLayers(layers, 'lint'),
fmt: await resolveConfigLayers(layers, 'fmt'),
staged: await resolveConfigLayers(layers, 'staged'),
},
filePath,
dependencies,
}),
);
65 changes: 18 additions & 47 deletions packages/rstack/tests/config/load-config/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import { afterEach, expect, test } from 'rstack/test';
import { define, loadRstackConfig } from '../../../src/config.ts';
Expand All @@ -6,33 +7,14 @@ import {
loadCliRstackConfig,
} from '../../../src/cli/config.ts';

type Deferred = ReturnType<typeof Promise.withResolvers<void>>;

type ConfigTestHooks = {
ready: Deferred;
release: Deferred;
};

declare global {
// rslint-disable-next-line no-var
var __rstackConfigTestHooks: ConfigTestHooks | undefined;
}

const state = getConfigState();
const configPath = (fileName: string): string =>
path.join(import.meta.dirname, fileName);
const loadConfigFile = (fileName: string) =>
loadRstackConfig({ configFilePath: configPath(fileName) });

const createTestHooks = (): ConfigTestHooks =>
(globalThis.__rstackConfigTestHooks = {
ready: Promise.withResolvers<void>(),
release: Promise.withResolvers<void>(),
});

afterEach(() => {
delete state.configPath;
delete globalThis.__rstackConfigTestHooks;
});

test('should discard a config session after loading fails', async () => {
Expand Down Expand Up @@ -94,39 +76,28 @@ test('should search cwd without inheriting the CLI config path', async () => {
);
});

test('should isolate parallel config sessions across top-level await', async () => {
const hooks = createTestHooks();
const firstLoad = loadConfigFile('parallel-first.config.ts');
await hooks.ready.promise;

const secondLoad = loadConfigFile('parallel-second.config.ts');
const parallelLoads = Promise.all([firstLoad, secondLoad]);

await secondLoad;
hooks.release.resolve();
const [firstResult, secondResult] = await parallelLoads;
// Native loader hooks and the fixture's globals must share one Node realm.
test('should isolate parallel config sessions across top-level await', () => {
const output = execFileSync(
process.execPath,
[configPath('parallel-load.ts'), 'success'],
{ encoding: 'utf8', timeout: 10_000 },
);

expect(firstResult.configs).toEqual({
app: { root: 'first' },
test: {},
});
expect(secondResult.configs).toEqual({
app: { root: 'second' },
lib: {},
});
expect(JSON.parse(output)).toEqual([
{ app: { root: 'first' }, test: {} },
{ app: { root: 'second' }, lib: {} },
]);
});

test('should keep a running session intact when another config fails', async () => {
const hooks = createTestHooks();
const successfulLoad = loadConfigFile('parallel-first.config.ts');
await hooks.ready.promise;

await expect(loadConfigFile('parallel-failing.config.ts')).rejects.toThrow(
'parallel config error',
test('should keep a running session intact when another config fails', () => {
const output = execFileSync(
process.execPath,
[configPath('parallel-load.ts'), 'failure'],
{ encoding: 'utf8', timeout: 10_000 },
);
hooks.release.resolve();

expect((await successfulLoad).configs).toEqual({
expect(JSON.parse(output)).toEqual({
app: { root: 'first' },
test: {},
});
Expand Down
36 changes: 36 additions & 0 deletions packages/rstack/tests/config/load-config/parallel-load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import assert from 'node:assert/strict';
import path from 'node:path';
import { loadRstackConfig } from '../../../src/config.ts';

type Deferred = ReturnType<typeof Promise.withResolvers<void>>;

declare global {
// rslint-disable-next-line no-var
var __rstackConfigTestHooks:
{ ready: Deferred; release: Deferred } | undefined;
}

const hooks = (globalThis.__rstackConfigTestHooks = {
ready: Promise.withResolvers<void>(),
release: Promise.withResolvers<void>(),
});
const load = (name: string) =>
loadRstackConfig({ configFilePath: path.join(import.meta.dirname, name) });
const firstLoad = load('parallel-first.config.ts');
await Promise.race([hooks.ready.promise, firstLoad]);

if (process.argv[2] === 'failure') {
await assert.rejects(load('parallel-failing.config.ts'), {
message: 'parallel config error',
});
hooks.release.resolve();
console.log(JSON.stringify((await firstLoad).configs));
} else {
const secondLoad = load('parallel-second.config.ts');
const parallelLoads = Promise.all([firstLoad, secondLoad]);
await secondLoad;
hooks.release.resolve();
console.log(
JSON.stringify((await parallelLoads).map(({ configs }) => configs)),
);
}
Loading