Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
453ad1f
Tests for non-priviledged commands
rugvedS07 Jul 30, 2025
35ba999
Basic workflow for testing lms
rugvedS07 Jul 30, 2025
fe8fd6b
Correct repo name
rugvedS07 Jul 30, 2025
6cf6377
Correct branch variable name
rugvedS07 Jul 30, 2025
050e5b8
Fetch branch
rugvedS07 Jul 30, 2025
612cfdc
Submodules true
rugvedS07 Jul 30, 2025
79b84ee
Proper dependency resolution
rugvedS07 Jul 30, 2025
ec8586a
Correct action path
rugvedS07 Jul 30, 2025
6c8de46
Another path attempt
rugvedS07 Jul 30, 2025
fef3a19
Better runner
rugvedS07 Jul 30, 2025
ea6ecad
Revert the runner and disable server stop tests
rugvedS07 Jul 30, 2025
772fbe5
Add testing for lms cli from lmstudiojs
rugvedS07 Jul 30, 2025
6a89423
Other grep command
rugvedS07 Jul 30, 2025
06978b4
Avoid shell execution
rugvedS07 Jul 30, 2025
8d52b32
Use heredoc
rugvedS07 Jul 30, 2025
bb58787
Cleaner solution with tee
rugvedS07 Jul 30, 2025
940b6fa
ENV variable solution
rugvedS07 Jul 30, 2025
b598e17
Better checkout logic
rugvedS07 Jul 30, 2025
2769c3b
tmate session
rugvedS07 Jul 30, 2025
e22b147
Specify port
rugvedS07 Jul 30, 2025
345b634
Change all the tests to use localhost and port 1234
rugvedS07 Jul 30, 2025
c566906
Change how runCommandSync works
rugvedS07 Jul 30, 2025
73eb4b2
Better chat tests
rugvedS07 Jul 30, 2025
7625e5e
Remove the tests which canont be run in a docker context
rugvedS07 Jul 30, 2025
2da91f1
Espace for quotes
rugvedS07 Jul 30, 2025
27970b1
Remove flags test
rugvedS07 Jul 30, 2025
aa3d44c
Rename all tests to be heavy
rugvedS07 Jul 30, 2025
a00b81f
CLI_PATH and remove help tests
rugvedS07 Jul 30, 2025
88f85f7
Revert package.json
rugvedS07 Jul 30, 2025
10c4dab
Fix unload error
rugvedS07 Jul 30, 2025
e7ea3b7
Make changes to tests
rugvedS07 Aug 27, 2025
6f782ab
Better list tests
rugvedS07 Aug 28, 2025
b08e7f6
Better load tests
rugvedS07 Aug 28, 2025
1bf5a66
Better status tests
rugvedS07 Aug 28, 2025
9c9b931
Better unload tests
rugvedS07 Aug 28, 2025
3ef6dd6
Better import tests
rugvedS07 Aug 28, 2025
6816aae
Skip chat tests for now
rugvedS07 Aug 28, 2025
6558999
Add comment
rugvedS07 Aug 28, 2025
7671c28
Ready file logic and CI changes
rugvedS07 Aug 28, 2025
7f1ac39
Add git to the container
rugvedS07 Aug 28, 2025
3e76aad
Remove docker
rugvedS07 Aug 28, 2025
10c57e8
add google/
rugvedS07 Aug 28, 2025
15ae229
Download model from cloudflare
rugvedS07 Aug 28, 2025
3a6eeba
Add -p to the other mkdir
rugvedS07 Aug 28, 2025
41b0587
Address comments
rugvedS07 Aug 29, 2025
a88255c
Remove hardcoded version
rugvedS07 Aug 29, 2025
b4bf902
Remove all --host --port
rugvedS07 Aug 29, 2025
9d8db72
Skip chat tests for now
rugvedS07 Aug 29, 2025
f63ba5b
Skip import test
rugvedS07 Aug 29, 2025
a493199
Use LMS FORCE PROD
rugvedS07 Aug 29, 2025
4d34d0f
Verbose tests for cli
rugvedS07 Aug 29, 2025
310c5da
Fix tests
rugvedS07 Aug 29, 2025
15aaf51
Add debug steps
rugvedS07 Aug 29, 2025
6ab503b
add another lms-clone for test
rugvedS07 Aug 29, 2025
e285a2c
Remove tmate and wget in different location
rugvedS07 Aug 29, 2025
b1279b2
Add --quiet and status
rugvedS07 Aug 29, 2025
e84a829
More lms status
rugvedS07 Aug 29, 2025
429da9a
Fix error in workflow
rugvedS07 Aug 29, 2025
03b478e
Add tmate again and remove all lms status
rugvedS07 Aug 29, 2025
fa5b715
Single step attempt
rugvedS07 Aug 29, 2025
2462d3b
wget correction
rugvedS07 Aug 29, 2025
1dcfd0a
Correct path
rugvedS07 Aug 29, 2025
b284e81
Remove force prod
rugvedS07 Aug 29, 2025
803fb6c
Temp use lms get
rugvedS07 Aug 29, 2025
7301385
Renable actual import tests
rugvedS07 Aug 29, 2025
c68f6fb
Create a valid gguf file
rugvedS07 Aug 29, 2025
9b9a36a
Use a test folder instead
rugvedS07 Aug 29, 2025
bb2e35d
Add separator logic
rugvedS07 Aug 29, 2025
4a2d982
Fix awk command
rugvedS07 Sep 2, 2025
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
118 changes: 118 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: LMS CLI Tests

# Group workflow runs by branch and cancel in-progress runs on new commits to avoid PR wastefulness
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened, labeled]

jobs:
build-and-test:
name: Build and Test LMS CLI
runs-on: ubuntu-latest
container:
image: lmstudio/llmster-preview:cpu
timeout-minutes: 30
steps:
- name: Install git on container
run: |
apt update
apt install -y git
- name: Determine lmstudio.js branch
id: branch
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
echo "$PR_BODY" > pr_body.txt

# Always look below separators first
LMS_JS_BRANCH=$(awk '/^-+$/{flag=1; next} flag && /lmstudio-js-branch:/{gsub(/.*lmstudio-js-branch:[ \t]*/, ""); gsub(/[ \t].*/, ""); print; exit}' pr_body.txt || true)

# If not found in separator sections, try anywhere in PR body
if [ -z "$LMS_JS_BRANCH" ]; then
LMS_JS_BRANCH=$(grep -oP 'lmstudio-js-branch:\s*\K\S+' pr_body.txt || true)
fi

# Fallback to main if not found
if [ -z "$LMS_JS_BRANCH" ]; then
LMS_JS_BRANCH="main"
fi

echo "branch=$LMS_JS_BRANCH" >> $GITHUB_OUTPUT

- name: Checkout lmstudio.js repo
uses: actions/checkout@v4
with:
repository: lmstudio-ai/lmstudio-js
ref: ${{ steps.branch.outputs.branch }}
path: lmstudio.js
submodules: true
- name: Fetch and checkout lms-cli branch
run: |
cd lmstudio.js/packages/lms-cli
git fetch origin ${{ github.head_ref }}:${{ github.head_ref }}
git checkout ${{ github.head_ref }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: lmstudio.js/package-lock.json

- name: Install dependencies
run: |
cd lmstudio.js
npm install

- name: Build lmstudio.js
run: |
cd lmstudio.js
npm run build

- name: Run daemon and tests
id: run
run: |
cd /app
echo "Starting up llmd..."
./daemon-run.sh &
DAEMON_PID=$!
LM_HOME="${HOME}/.lmstudio"
MAX_WAIT=120
START_TIME=$(date +%s)
# Check for the ready files
while true; do
# Check for any .ready file
if ls "${LM_HOME}"/.ready* 1> /dev/null 2>&1; then
break
fi
CURRENT_TIME=$(date +%s)
ELAPSED=$((CURRENT_TIME - START_TIME))
if [ $ELAPSED -ge $MAX_WAIT ]; then
echo "Timed out waiting for llmster to start after ${MAX_WAIT} seconds."
break
fi
sleep 1
done

# Setup model
# mkdir -p /.lmstudio/hub/models/google
# cd /.lmstudio/hub/models/google
# lms clone google/gemma-3-1b
# mkdir -p /.lmstudio/models/lmstudio-community/gemma-3-1b-it-GGUF
# cd /.lmstudio/models/lmstudio-community/gemma-3-1b-it-GGUF
# if ! command -v wget &> /dev/null; then
# apt-get update
# apt-get install -y wget
# fi
# wget --quiet -P /tmp https://models.lmstudio.ai/models/gemma-3-1b-it-Q4_K_M.gguf && mv /tmp/gemma-3-1b-it-Q4_K_M.gguf gemma-3-1b-it-Q4_K_M.gguf

Comment on lines +103 to +113
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be removed?

lms get google/gemma-3-1b -y

# Run tests
cd $GITHUB_WORKSPACE/lmstudio.js
npm run test-cli -- --verbose
106 changes: 106 additions & 0 deletions src/subcommands/chat.heavy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import path from "path";
import { TEST_CLI_PATH, testRunCommandSync } from "../test-utils.js";

// We skip chat tests to because we don't have max_tokens here.
describe.skip("chat heavy", () => {
const cliPath = path.join(__dirname, TEST_CLI_PATH);
const modelIdentifier = "test-model";
const modelToUse = "gemma-3-1b";

beforeAll(async () => {
// Ensure the test model is loaded
const { status } = testRunCommandSync("node", [
cliPath,
"load",
modelToUse,
"--identifier",
modelIdentifier,
"--yes",
]);
if (status !== 0) {
throw new Error(`Failed to load test model: ${modelIdentifier}`);
}
}, 30000);

afterAll(async () => {
// Clean up by unloading the model
const { status } = testRunCommandSync("node", [cliPath, "unload", modelIdentifier]);
if (status !== 0) {
console.warn(`Failed to unload test model: ${modelIdentifier}`);
}
}, 10000);

it("should respond to simple prompt with specific model", () => {
const { status, stdout, stderr } = testRunCommandSync("node", [
cliPath,
"chat",
modelIdentifier,
"--prompt",
'"What is 2+2? Answer briefly:"',
]);

if (status !== 0) console.error("Chat stderr:", stderr);
expect(status).toBe(0);
expect(stdout.toLowerCase()).toContain("4");
}, 15000);

it("should use custom system prompt", () => {
const { status, stdout, stderr } = testRunCommandSync("node", [
cliPath,
"chat",
modelIdentifier,
"--prompt",
'"What is your role?"',
"--system-prompt",
'"You are a helpful assistant."',
]);

if (status !== 0) console.error("Chat stderr:", stderr);
expect(status).toBe(0);
expect(stdout.toLowerCase()).toMatch(/(assistant|help)/);
}, 15000);

it("should display stats when --stats flag is used", () => {
const { status, stderr } = testRunCommandSync("node", [
cliPath,
"chat",
modelIdentifier,
"--prompt",
'"Hi"',
"--stats",
]);

if (status !== 0) console.error("Chat stderr:", stderr);
expect(status).toBe(0);
expect(stderr).toContain("Prediction Stats:");
expect(stderr).toContain("Stop Reason:");
expect(stderr).toContain("Tokens/Second:");
}, 15000);

it("should work with default model when no model specified", () => {
const { status, stdout, stderr } = testRunCommandSync("node", [
cliPath,
"chat",
"--prompt",
"\"Say hello. Respond with just 'hello'\"",
]);

if (status !== 0) console.error("Chat stderr:", stderr);
expect(status).toBe(0);
expect(stdout.toLowerCase()).toContain("hello");
}, 15000);

it("should fail gracefully with non-existent model", () => {
const { status, stderr } = testRunCommandSync("node", [
cliPath,
"chat",
"non-existent-model",
"--prompt",
'"test"',
]);

expect(status).not.toBe(0);
expect(stderr).toContain("not found");
expect(stderr).toContain("lms ls");
});
});
Loading