Skip to content
Closed
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
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ Finally, there are **simulation tests**. These tests reach out to Copilot API en

Because LLM results are both random and costly, they are cached within the repo in `test/simulation/cache`. This means rerunning the simulation tests and benefiting from the cache will make the test run be both faster as well as deterministic.

**Important:** Before running simulation tests, you must first pull the Git LFS files:

```
git lfs pull
```

The cache files (`*.sqlite`) are stored using Git LFS. If you skip this step, you will see a `TypeError: database.query is not a function` error because the SQLite database files haven't been downloaded.

You can run the simulation tests with:

```
Expand Down
73 changes: 70 additions & 3 deletions test/base/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,62 @@ async function getGitRoot(cwd: string): Promise<string> {
return stdout.trim();
}

// LFS pointer files start with this header
const LFS_POINTER_HEADER = 'version https://git-lfs.github.com/spec/v1';

/**
* Checks if a file is a Git LFS pointer file instead of the actual content.
* LFS pointer files start with "version https://git-lfs.github.com/spec/v1"
*/
function isLfsPointerFile(filePath: string): boolean {
let fd: number | undefined;
try {
// Read only enough bytes to check the LFS header
fd = fs.openSync(filePath, 'r');
const buffer = Buffer.alloc(LFS_POINTER_HEADER.length);
fs.readSync(fd, buffer, 0, LFS_POINTER_HEADER.length, 0);
const content = buffer.toString('utf8');
return content.startsWith(LFS_POINTER_HEADER);
} catch {
return false;
} finally {
if (fd !== undefined) {
try {
fs.closeSync(fd);
} catch {
// Ignore close errors
}
}
}
}

/**
* Error thrown when Git LFS files haven't been pulled
*/
export class GitLfsNotPulledError extends Error {
constructor(filePath: string) {
const message = `
================================================================================
GIT LFS FILES NOT PULLED
================================================================================

The file "${filePath}" is a Git LFS pointer file, not the actual SQLite database.

This typically happens when you clone the repository without pulling the LFS files.

To fix this, run:

git lfs pull

Then try running the simulation tests again.

================================================================================
`;
super(message);
this.name = 'GitLfsNotPulledError';
}
}

export class Cache extends EventEmitter {
private static _Instance: Cache | undefined;
static get Instance() {
Expand All @@ -50,16 +106,23 @@ export class Cache extends EventEmitter {
this.layersPath = path.join(this.cachePath, 'layers');
this.externalLayersPath = process.env.EXTERNAL_CACHE_LAYERS_PATH;

if (!fs.existsSync(path.join(this.cachePath, 'base.sqlite'))) {
throw new Error(`Base cache file does not exist as ${path.join(this.cachePath, 'base.sqlite')}.`);
const baseSqlitePath = path.join(this.cachePath, 'base.sqlite');

if (!fs.existsSync(baseSqlitePath)) {
throw new Error(`Base cache file does not exist as ${baseSqlitePath}.`);
}

// Check if the base.sqlite file is an LFS pointer instead of the actual SQLite database
if (isLfsPointerFile(baseSqlitePath)) {
throw new GitLfsNotPulledError(baseSqlitePath);
}

if (this.externalLayersPath && !fs.existsSync(this.externalLayersPath)) {
throw new Error(`External layers cache directory provided but it does not exist at ${this.externalLayersPath}.`);
}

fs.mkdirSync(this.layersPath, { recursive: true });
this.base = new Keyv(new KeyvSqlite(path.join(this.cachePath, 'base.sqlite')));
this.base = new Keyv(new KeyvSqlite(baseSqlitePath));

this.layers = new Map();
let layerFiles = fs.readdirSync(this.layersPath)
Expand All @@ -74,6 +137,10 @@ export class Cache extends EventEmitter {
}

for (const layerFile of layerFiles) {
// Check if layer file is an LFS pointer instead of the actual SQLite database
if (isLfsPointerFile(layerFile)) {
throw new GitLfsNotPulledError(layerFile);
}
const name = path.basename(layerFile, path.extname(layerFile));
this.layers.set(name, new Keyv(new KeyvSqlite(layerFile)));
}
Expand Down