Skip to content
Merged
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
27 changes: 27 additions & 0 deletions docs/rfcs/component-loading-rewrite/SNAPSHOT-CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,30 @@ V1 and the rewritten loader and the harness reports zero diffs), add the
relevant field here, update `snapshot.ts`, and rerun the V1-vs-V1 baseline. If
V1-vs-V1 isn't zero on the new field, the field needs a normalization rule
before it can join the contract.

## Command coverage

The harness wraps `Workspace.componentLoader`, so any command that loads
components via `workspace.get` / `workspace.getMany` / `workspace.getIfExist`
is covered. Confirmed:

- `bit status` — covered (loads components, then compares against scope state).
- `bit show <id>` — covered (calls `host.get(id)` which routes to the loader).

**Not covered, by design:**

- `bit list` — only reads `consumer.bitMap.bitmapIdsFromCurrentLane`. No
components are loaded. The harness can't observe what doesn't run.

If you find a command that loads components but doesn't trigger the harness,
that's a real gap — either the command is using a different loader path
(`consumer.loadComponents` directly, scope-only loading, etc.) or the wrapping
in `Workspace`'s constructor missed something. File it as a follow-up.

## Sample rate (`BIT_LOADER_DIFF_SAMPLE`)

On large workspaces (bit7 itself, ~300 components), running both loaders for
every call doubles cache footprint and can OOM Node's default 4GB heap. Set
`BIT_LOADER_DIFF_SAMPLE=10` to run the partner only every 10th call. The
trade-off: sampling can miss regressions that only manifest on the
non-sampled calls. Use the lowest sample rate the workspace can afford.
27 changes: 21 additions & 6 deletions e2e/harmony/loader-diff-harness.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,39 @@ describe('loader diff harness — V1-vs-V1 baseline', function () {
`expected zero diffs but got: ${JSON.stringify(dataLines(lines), null, 2)}`
).to.have.lengthOf(0);
});

it('bit show writes a harness header and produces zero diffs', () => {
helper.command.runCmd(`bit show comp1`, helper.scopes.localPath, 'pipe', undefined, false, {
BIT_LOADER_DIFF: '1',
BIT_LOADER_DIFF_OUT: logPath,
});
const lines = readDiffLog(logPath);
expect(
lines.filter((l) => l.header),
'expected at least one header line'
).to.have.length.greaterThan(0);
expect(
dataLines(lines),
`expected zero diffs but got: ${JSON.stringify(dataLines(lines), null, 2)}`
).to.have.lengthOf(0);
});
});

// TODO: re-enable once the harness's memory footprint is acceptable on workspaces
// with scope state. Today, running two WorkspaceComponentLoader instances in parallel
// doubles the cache footprint and can OOM Node's default 4GB heap even on tiny
// workspaces. Likely needs a sampling mode or a lighter-weight partner.
describe.skip('workspace with tagged + modified component', () => {
describe('workspace with tagged + modified component', () => {
before(() => {
helper.scopeHelper.setWorkspaceWithRemoteScope();
helper.fixtures.populateComponents(1);
helper.command.tagAllWithoutBuild();
helper.fs.appendFile('comp1/index.js', '\n// modified after tag');
});

it('bit status on a modified component produces zero diffs', () => {
it('bit status on a modified component produces zero diffs (sampled)', () => {
// BIT_LOADER_DIFF_SAMPLE=50 — workspaces with scope state make many loader
// calls per command; sampling caps the partner's footprint.
helper.command.runCmd(`bit status`, helper.scopes.localPath, 'pipe', undefined, false, {
BIT_LOADER_DIFF: '1',
BIT_LOADER_DIFF_OUT: logPath,
BIT_LOADER_DIFF_SAMPLE: '50',
});
const lines = readDiffLog(logPath);
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export interface LoaderDiffHarnessOptions {
outputPath?: string;
/** Tag written into each log line, e.g. "v1-vs-v1" or "v1-vs-v2". */
comparisonLabel: string;
/**
* Sample rate. If `N > 1`, only every Nth call runs the partner loader.
* Reduces overhead on large workspaces where running both loaders for every
* call would OOM Node's default heap. Default: 1 (every call).
*/
sampleEvery?: number;
}

/**
Expand All @@ -38,6 +44,7 @@ export class LoaderDiffHarness {
private partner: WorkspaceComponentLoader | null = null;
private readonly outputPath: string;
private readonly comparisonLabel: string;
private readonly sampleEvery: number;
private callIndex = 0;
private writeFailureLogged = false;

Expand All @@ -49,6 +56,7 @@ export class LoaderDiffHarness {
) {
this.outputPath = options.outputPath ?? path.join(os.tmpdir(), 'bit-loader-diff.jsonl');
this.comparisonLabel = options.comparisonLabel;
this.sampleEvery = Math.max(1, Math.floor(options.sampleEvery ?? 1));
this.writeHeader();
}

Expand Down Expand Up @@ -116,6 +124,7 @@ export class LoaderDiffHarness {
runPartner: () => Promise<Component[]>
): Promise<void> {
const callId = this.callIndex++;
if (callId % this.sampleEvery !== 0) return;
let partnerComponents: Component[];
try {
partnerComponents = await runPartner();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,15 @@ export function loaderDiffMode(): string | null {
if (raw === '1' || raw.toLowerCase() === 'true') return 'v1-vs-v1';
return raw;
}

/**
* Returns the sample rate from `BIT_LOADER_DIFF_SAMPLE`. Default 1 (every call).
* Use larger values on workspaces big enough that running both loaders for every
* call would OOM, e.g. `BIT_LOADER_DIFF_SAMPLE=10`.
*/
export function loaderDiffSampleEvery(): number {
const raw = process.env.BIT_LOADER_DIFF_SAMPLE;
if (!raw) return 1;
const parsed = Number.parseInt(raw, 10);
return Number.isFinite(parsed) && parsed >= 1 ? parsed : 1;
}
8 changes: 6 additions & 2 deletions scopes/workspace/workspace/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ import { CompFiles } from './workspace-component/comp-files';
import { Filter } from './filter';
import type { ComponentStatusLegacy, ComponentStatusResult } from './workspace-component/component-status-loader';
import { ComponentStatusLoader } from './workspace-component/component-status-loader';
import { LoaderDiffHarness, loaderDiffMode } from './workspace-component/loader-diff';
import { LoaderDiffHarness, loaderDiffMode, loaderDiffSampleEvery } from './workspace-component/loader-diff';
import execa from 'execa';
import { getGitExecutablePath } from '@teambit/git.modules.git-executable';
import { VERSION_ZERO } from '@teambit/objects';
Expand Down Expand Up @@ -273,7 +273,11 @@ export class Workspace implements ComponentFactory {
primaryLoader,
() => new WorkspaceComponentLoader(this, logger, dependencyResolver, envs, aspectLoader),
logger,
{ comparisonLabel: diffMode, outputPath: process.env.BIT_LOADER_DIFF_OUT }
{
comparisonLabel: diffMode,
outputPath: process.env.BIT_LOADER_DIFF_OUT,
sampleEvery: loaderDiffSampleEvery(),
}
) as unknown as WorkspaceComponentLoader;
} else {
this.componentLoader = primaryLoader;
Expand Down