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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add Getting Started walkthrough to VS Code extension ([#116])
- Four-step guided setup: install toolchain, verify with doctor, create project, build
- Add status bar integration showing toolchain health at a glance ([#116])
- Update VS Code extension tests and QA docs after LLVM removal ([#127])
- Remove `inf-llc`, `rust-lld`, `libLLVM` references from e2e tests and doctor tests
- Update fake `infs` shell script to use flat toolchain layout (`TOOLCHAIN_DIR/infc`, no `bin/` subdirectory)
- Simplify `buildFakeInfcArchive()` to emit only `infc` binary
- Update doctor check expectations from 6 to 5 checks (single `infc` check replaces `inf-llc`, `rust-lld`, `libLLVM`)
- Change "missing lib directory triggers doctor warning" to "missing infc triggers doctor failure"

### Language

Expand Down Expand Up @@ -281,3 +287,4 @@ Initial tagged release.
[#116]: https://github.com/Inferara/inference/pull/116
[#125]: https://github.com/Inferara/inference/pull/125
[#126]: https://github.com/Inferara/inference/pull/126
[#127]: https://github.com/Inferara/inference/pull/127
4 changes: 3 additions & 1 deletion book/appendix/llvm-features.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Appendix B: LLVM Features
# Appendix B: LLVM Features (Legacy)

> **Note:** Starting from the version after v0.0.1-beta.3, the Inference compiler no longer uses LLVM. WebAssembly is generated directly via `wasm-encoder`. This appendix is preserved as a reference for users on older versions.

| LLVM Feature | Stage | Description |
|--------------|-------| ------------|
Expand Down
6 changes: 3 additions & 3 deletions editors/vscode/QA_GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Inference VS Code Extension -- Manual QA Guide

**Version:** 0.0.3
**Branch:** `116-integrate-infs-to-vscode-extension`
**Date:** 2026-02-02
**Branch:** `127-update-vscode-extension-after-removing-llvm`
**Date:** 2026-02-18

---

Expand Down Expand Up @@ -46,7 +46,7 @@ Many QA cases below are covered by automated tests (`npm test`). Cases marked wi
| 0.1 | `npm install` in `editors/vscode/` | Installs without errors |
| 0.2 | `npm run build` | Builds `dist/extension.js` without errors |
| 0.3 | `npm run build:prod` | Production build succeeds |
| 0.4 | `npm test` | All 220 tests pass, 0 failures |
| 0.4 | `npm test` | All 216 tests pass, 0 failures |
| 0.5 | `npm run package` | Produces `inference-0.0.3.vsix` without errors |

---
Expand Down
4 changes: 2 additions & 2 deletions editors/vscode/src/test/doctor-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('formatDoctorChecks (QA Section 4.2)', () => {
const result = makeDoctorResult({
checks: [
{ name: 'infs binary', status: 'ok', message: 'Found at /usr/bin/infs' },
{ name: 'libLLVM', status: 'warn', message: 'Not found' },
{ name: 'infc', status: 'warn', message: 'Not found' },
{ name: 'Default toolchain', status: 'fail', message: 'Not installed' },
],
summary: 'Some checks failed.',
Expand All @@ -32,7 +32,7 @@ describe('formatDoctorChecks (QA Section 4.2)', () => {
const lines = formatDoctorChecks(result);

assert.ok(lines.some((l) => l.includes('[OK]') && l.includes('infs binary')));
assert.ok(lines.some((l) => l.includes('[WARN]') && l.includes('libLLVM')));
assert.ok(lines.some((l) => l.includes('[WARN]') && l.includes('infc')));
assert.ok(lines.some((l) => l.includes('[FAIL]') && l.includes('Default toolchain')));
});

Expand Down
15 changes: 7 additions & 8 deletions editors/vscode/src/test/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ describe('parseDoctorOutput', () => {
' [OK] Platform: Detected linux-x64',
' [OK] Toolchain directory: Found at /home/user/.inference',
' [OK] Default toolchain: Set to 0.1.0',
' [OK] inf-llc: Found inf-llc in PATH',
' [OK] rust-lld: Found rust-lld in PATH',
' [OK] infc: Found infc in PATH',
'',
'All checks passed. The toolchain is ready to use.',
].join('\n');

const result = parseDoctorOutput(stdout);

assert.strictEqual(result.checks.length, 6);
assert.strictEqual(result.checks.length, 5);
assert.strictEqual(result.hasErrors, false);
assert.strictEqual(result.hasWarnings, false);
assert.strictEqual(
Expand All @@ -45,7 +44,7 @@ describe('parseDoctorOutput', () => {
'',
' [OK] infs binary: Found at /home/user/.inference/bin/infs',
' [OK] Platform: Detected linux-x64',
' [WARN] libLLVM: Not found in /path. Some features may not work.',
' [WARN] Default toolchain: No default toolchain set.',
'',
'Some warnings were found. The toolchain may work but could have issues.',
].join('\n');
Expand All @@ -56,7 +55,7 @@ describe('parseDoctorOutput', () => {
assert.strictEqual(result.hasErrors, false);
assert.strictEqual(result.hasWarnings, true);
assert.strictEqual(result.checks[2].status, 'warn');
assert.strictEqual(result.checks[2].name, 'libLLVM');
assert.strictEqual(result.checks[2].name, 'Default toolchain');
assert.strictEqual(
result.summary,
'Some warnings were found. The toolchain may work but could have issues.',
Expand Down Expand Up @@ -90,7 +89,7 @@ describe('parseDoctorOutput', () => {
'',
' [OK] infs binary: Found at /usr/local/bin/infs',
' [WARN] Default toolchain: No default toolchain set.',
' [FAIL] inf-llc: Not found.',
' [FAIL] infc: Not found.',
'',
"Some checks failed. Run 'infs install' to install the toolchain.",
].join('\n');
Expand Down Expand Up @@ -155,7 +154,7 @@ describe('parseDoctorOutput', () => {
'Checking Inference toolchain installation...',
'',
' [OK] infs binary: Found at /usr/local/bin/infs',
' [WARN] libLLVM: Not found',
' [WARN] Default toolchain: No default set',
'',
'Some warnings found.',
].join('\r\n');
Expand All @@ -173,7 +172,7 @@ describe('parseDoctorOutput', () => {
it('handles mixed LF and CRLF', () => {
const stdout =
' [OK] Platform: Detected linux-x64\r\n' +
' [FAIL] inf-llc: Not found\n' +
' [FAIL] infc: Not found\n' +
'Checks failed.\n';

const result = parseDoctorOutput(stdout);
Expand Down
157 changes: 27 additions & 130 deletions editors/vscode/src/test/e2e-installation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import { extractArchive } from '../utils/extract';
import { exec } from '../utils/exec';
import { parseDoctorOutput } from '../toolchain/doctor';
import { findLatestRelease, type ReleaseEntry } from '../toolchain/manifest';
import { detectPlatform } from '../toolchain/platform';

/**
* End-to-end installation tests.
*
Expand All @@ -26,8 +24,6 @@ import { detectPlatform } from '../toolchain/platform';
* works without needing the `vscode` module.
*/

const platform = detectPlatform();

// These e2e tests require a Unix platform (shell scripts as fake binaries).
const isUnix = process.platform !== 'win32';

Expand Down Expand Up @@ -99,11 +95,8 @@ case "\$1" in
echo "\${VERSION}" > "\${INFERENCE_HOME}/default"

mkdir -p "\${INFERENCE_HOME}/bin"
for bin in infc inf-llc rust-lld; do
SOURCE="\${TOOLCHAIN_DIR}/bin/\${bin}"
if [ ! -f "\${SOURCE}" ]; then
SOURCE="\${TOOLCHAIN_DIR}/\${bin}"
fi
for bin in infc; do
SOURCE="\${TOOLCHAIN_DIR}/\${bin}"
if [ -f "\${SOURCE}" ]; then
ln -sf "\${SOURCE}" "\${INFERENCE_HOME}/bin/\${bin}"
fi
Expand Down Expand Up @@ -149,46 +142,18 @@ case "\$1" in
HAS_WARN=1
fi

# Check inf-llc
# Check infc
DEFAULT_VERSION=\$(cat "\${INFERENCE_HOME}/default" 2>/dev/null || echo "")
if [ -n "\${DEFAULT_VERSION}" ]; then
INF_LLC="\${INFERENCE_HOME}/toolchains/\${DEFAULT_VERSION}/bin/inf-llc"
if [ -f "\${INF_LLC}" ]; then
echo " [OK] inf-llc: Found at \${INF_LLC}"
else
echo " [FAIL] inf-llc: Not found"
HAS_FAIL=1
fi
fi

# Check rust-lld
if [ -n "\${DEFAULT_VERSION}" ]; then
RUST_LLD="\${INFERENCE_HOME}/toolchains/\${DEFAULT_VERSION}/bin/rust-lld"
if [ -f "\${RUST_LLD}" ]; then
echo " [OK] rust-lld: Found at \${RUST_LLD}"
INFC="\${INFERENCE_HOME}/toolchains/\${DEFAULT_VERSION}/infc"
if [ -f "\${INFC}" ]; then
echo " [OK] infc: Found at \${INFC}"
else
echo " [FAIL] rust-lld: Not found"
echo " [FAIL] infc: Not found"
HAS_FAIL=1
fi
fi

# Check libLLVM (Linux only)
if [ -n "\${DEFAULT_VERSION}" ]; then
LIB_DIR="\${INFERENCE_HOME}/toolchains/\${DEFAULT_VERSION}/lib"
if [ -d "\${LIB_DIR}" ]; then
FOUND_LIB=\$(ls "\${LIB_DIR}"/libLLVM*.so* 2>/dev/null | head -1)
if [ -n "\${FOUND_LIB}" ]; then
echo " [OK] libLLVM: Found \${FOUND_LIB}"
else
echo " [WARN] libLLVM: Not found in \${LIB_DIR}"
HAS_WARN=1
fi
else
echo " [WARN] libLLVM: Library directory not found at \${LIB_DIR}"
HAS_WARN=1
fi
fi

echo ""
if [ "\${HAS_FAIL}" = "1" ]; then
echo "Some checks failed. Run 'infs install' to install the toolchain."
Expand All @@ -215,31 +180,14 @@ esac
*
* Structure mirrors the real GitHub release:
* ./infc
* ./bin/inf-llc
* ./bin/rust-lld
* ./lib/libLLVM.so.21.1-rust-1.94.0-nightly
*/
function buildFakeInfcArchive(archivePath: string, opts?: { skipLib?: boolean }): void {
function buildFakeInfcArchive(archivePath: string): void {
const sourceDir = archivePath + '-source';
fs.mkdirSync(path.join(sourceDir, 'bin'), { recursive: true });
fs.mkdirSync(sourceDir, { recursive: true });

fs.writeFileSync(path.join(sourceDir, 'infc'), '#!/bin/sh\necho "infc 0.0.1-test"\n');
fs.chmodSync(path.join(sourceDir, 'infc'), 0o755);

fs.writeFileSync(path.join(sourceDir, 'bin', 'inf-llc'), '#!/bin/sh\necho "inf-llc"\n');
fs.chmodSync(path.join(sourceDir, 'bin', 'inf-llc'), 0o755);

fs.writeFileSync(path.join(sourceDir, 'bin', 'rust-lld'), '#!/bin/sh\necho "rust-lld"\n');
fs.chmodSync(path.join(sourceDir, 'bin', 'rust-lld'), 0o755);

if (!opts?.skipLib) {
fs.mkdirSync(path.join(sourceDir, 'lib'), { recursive: true });
fs.writeFileSync(
path.join(sourceDir, 'lib', 'libLLVM.so.21.1-rust-1.94.0-nightly'),
'fake-llvm-lib',
);
}

execSync(`tar -czf "${archivePath}" -C "${sourceDir}" .`);
fs.rmSync(sourceDir, { recursive: true });
}
Expand Down Expand Up @@ -448,56 +396,33 @@ describe('e2e installation', { skip: !isUnix ? 'Unix-only tests' : undefined },
const toolchainDir = path.join(home, 'toolchains', '0.0.1-test');
assert.ok(fs.existsSync(toolchainDir), 'toolchain dir should exist');
assert.ok(fs.existsSync(path.join(toolchainDir, 'infc')), 'infc should exist');
assert.ok(fs.existsSync(path.join(toolchainDir, 'bin', 'inf-llc')), 'inf-llc should exist');
assert.ok(fs.existsSync(path.join(toolchainDir, 'bin', 'rust-lld')), 'rust-lld should exist');
});

it('creates the lib directory with libLLVM', () => {
const libDir = path.join(home, 'toolchains', '0.0.1-test', 'lib');
assert.ok(fs.existsSync(libDir), 'lib/ directory should exist');
const files = fs.readdirSync(libDir);
const hasLlvm = files.some(
(f) => f.startsWith('libLLVM') && f.includes('.so'),
);
assert.ok(hasLlvm, `Expected libLLVM*.so in ${libDir}, found: ${files}`);
});

it('creates symlinks in bin directory', () => {
const binDir = path.join(home, 'bin');
assert.ok(fs.existsSync(path.join(binDir, 'infc')), 'infc symlink should exist');
assert.ok(fs.existsSync(path.join(binDir, 'inf-llc')), 'inf-llc symlink should exist');
assert.ok(fs.existsSync(path.join(binDir, 'rust-lld')), 'rust-lld symlink should exist');
});
});

describe('missing lib directory triggers doctor warning', () => {
describe('missing infc triggers doctor failure', () => {
let parsed: ReturnType<typeof parseDoctorOutput>;
let doctorWarnings: boolean;
let doctorFailed: boolean;

before(async () => {
const home = path.join(tmpDir, 'nolib-home');
const home = path.join(tmpDir, 'noinfc-home');
const binDir = path.join(home, 'bin');
const version = '0.0.1-nolib';
const version = '0.0.1-noinfc';
const toolchainDir = path.join(home, 'toolchains', version);

// Manually create toolchain structure WITHOUT lib/
fs.mkdirSync(path.join(toolchainDir, 'bin'), { recursive: true });
fs.mkdirSync(toolchainDir, { recursive: true });
fs.mkdirSync(binDir, { recursive: true });

fs.writeFileSync(path.join(toolchainDir, 'infc'), '#!/bin/sh\necho infc\n');
fs.chmodSync(path.join(toolchainDir, 'infc'), 0o755);
fs.writeFileSync(path.join(toolchainDir, 'bin', 'inf-llc'), '#!/bin/sh\necho inf-llc\n');
fs.chmodSync(path.join(toolchainDir, 'bin', 'inf-llc'), 0o755);
fs.writeFileSync(path.join(toolchainDir, 'bin', 'rust-lld'), '#!/bin/sh\necho rust-lld\n');
fs.chmodSync(path.join(toolchainDir, 'bin', 'rust-lld'), 0o755);

fs.writeFileSync(path.join(home, 'default'), version);

// Copy infs binary from a previously downloaded archive
const manifest = await fetchJson<ReleaseEntry[]>(`${baseUrl}/releases.json`);
const match = findLatestRelease(manifest, { id: 'linux-x64' });
assert.ok(match);
const archiveTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'infs-nolib-'));
const archiveTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'infs-noinfc-'));
const archivePath = path.join(archiveTmp, 'infs.tar.gz');
try {
await downloadFile(match.fileUrl, { destPath: archivePath });
Expand All @@ -506,16 +431,6 @@ describe('e2e installation', { skip: !isUnix ? 'Unix-only tests' : undefined },
fs.rmSync(archiveTmp, { recursive: true, force: true });
}

// Create symlinks
for (const bin of ['infc', 'inf-llc', 'rust-lld']) {
const source = bin === 'infc'
? path.join(toolchainDir, bin)
: path.join(toolchainDir, 'bin', bin);
const target = path.join(binDir, bin);
try { fs.unlinkSync(target); } catch { /* ignore */ }
fs.symlinkSync(source, target);
}

const infsPath = path.join(binDir, 'infs');
const sep = ':';
const augmentedPath = `${binDir}${sep}${process.env['PATH'] ?? ''}`;
Expand All @@ -526,26 +441,26 @@ describe('e2e installation', { skip: !isUnix ? 'Unix-only tests' : undefined },
});

parsed = parseDoctorOutput(doctorResult.stdout);
doctorWarnings = doctorResult.exitCode !== 0 || parsed.hasErrors || parsed.hasWarnings;
doctorFailed = doctorResult.exitCode !== 0 || parsed.hasErrors;
});

it('reports doctor warnings', () => {
assert.strictEqual(doctorWarnings, true);
it('reports doctor failure', () => {
assert.strictEqual(doctorFailed, true);
});

it('libLLVM check reports warning', () => {
const libCheck = parsed.checks.find(
(c) => c.name === 'libLLVM',
it('infc check reports failure', () => {
const infcCheck = parsed.checks.find(
(c) => c.name === 'infc',
);
assert.ok(libCheck, 'Should have a libLLVM check');
assert.strictEqual(libCheck.status, 'warn');
assert.ok(infcCheck, 'Should have an infc check');
assert.strictEqual(infcCheck.status, 'fail');
});

it('non-lib checks still pass', () => {
const nonLibChecks = parsed.checks.filter(
(c) => c.name !== 'libLLVM',
it('non-infc checks still pass', () => {
const otherChecks = parsed.checks.filter(
(c) => c.name !== 'infc',
);
for (const check of nonLibChecks) {
for (const check of otherChecks) {
assert.strictEqual(
check.status,
'ok',
Expand Down Expand Up @@ -756,24 +671,6 @@ describe('e2e installation', { skip: !isUnix ? 'Unix-only tests' : undefined },
assert.ok(fs.existsSync(path.join(extractDir, 'infc')));
});

it('has inf-llc inside bin/', () => {
assert.ok(fs.existsSync(path.join(extractDir, 'bin', 'inf-llc')));
});

it('has rust-lld inside bin/', () => {
assert.ok(fs.existsSync(path.join(extractDir, 'bin', 'rust-lld')));
});

it('has lib/ directory with libLLVM', () => {
const libDir = path.join(extractDir, 'lib');
assert.ok(fs.existsSync(libDir));
const files = fs.readdirSync(libDir);
assert.ok(
files.some((f) => f.startsWith('libLLVM') && f.includes('.so')),
`Expected libLLVM*.so in lib/, found: ${files}`,
);
});

it('binaries have executable permissions', () => {
const infcStat = fs.statSync(path.join(extractDir, 'infc'));
assert.ok(
Expand Down