Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 9622b91

Browse files
authored
Feat cortexcpp e2e cortexllamacpp nightly (#1744)
1 parent 0cec2fe commit 9622b91

File tree

5 files changed

+239
-0
lines changed

5 files changed

+239
-0
lines changed

.github/workflows/cortex-cpp-quality-gate.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ on:
55
types: [opened, synchronize, reopened, ready_for_review]
66
paths: ["engine/**", ".github/workflows/cortex-cpp-quality-gate.yml"]
77
workflow_dispatch:
8+
schedule:
9+
- cron: '0 22 * * *'
810

911
env:
1012
LLM_MODEL_URL: https://delta.jan.ai/tinyllama-1.1b-chat-v0.3.Q2_K.gguf
@@ -149,6 +151,34 @@ jobs:
149151
env:
150152
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
151153

154+
- name: Run e2e tests
155+
if: github.event_name == 'schedule' && runner.os != 'Windows' && github.event.pull_request.draft == false
156+
run: |
157+
cd engine
158+
cp build/cortex build/cortex-nightly
159+
cp build/cortex build/cortex-beta
160+
python -m pip install --upgrade pip
161+
python -m pip install -r e2e-test/requirements.txt
162+
python e2e-test/cortex-llamacpp-e2e-nightly.py
163+
rm build/cortex-nightly
164+
rm build/cortex-beta
165+
env:
166+
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
167+
168+
- name: Run e2e tests
169+
if: github.event_name == 'schedule' && runner.os == 'Windows' && github.event.pull_request.draft == false
170+
run: |
171+
cd engine
172+
cp build/cortex.exe build/cortex-nightly.exe
173+
cp build/cortex.exe build/cortex-beta.exe
174+
python -m pip install --upgrade pip
175+
python -m pip install -r e2e-test/requirements.txt
176+
python e2e-test/cortex-llamacpp-e2e-nightly.py
177+
rm build/cortex-nightly.exe
178+
rm build/cortex-beta.exe
179+
env:
180+
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
181+
152182
- name: Pre-package
153183
run: |
154184
cd engine
@@ -197,12 +227,28 @@ jobs:
197227
submodules: 'recursive'
198228

199229
- name: Run Docker
230+
if: github.event_name != 'schedule'
231+
run: |
232+
docker build \
233+
--build-arg REMOTE_CACHE_URL="${{ secrets.MINIO_ENDPOINT }}/vcpkg-cache" \
234+
--build-arg MINIO_ENDPOINT_URL="${{ secrets.MINIO_ENDPOINT }}" \
235+
--build-arg MINIO_ACCESS_KEY="${{ secrets.MINIO_ACCESS_KEY_ID }}" \
236+
--build-arg MINIO_SECRET_KEY="${{ secrets.MINIO_SECRET_ACCESS_KEY }}" \
237+
-t menloltd/cortex:test -f docker/Dockerfile.cache .
238+
docker run -it -d -p 3928:39281 --name cortex menloltd/cortex:test
239+
sleep 20
240+
241+
- name: Run Docker
242+
if: github.event_name == 'schedule'
200243
run: |
244+
latest_prerelease=$(curl -s https://api.github.com/repos/cortexcpp/cortex.cpp/releases | jq -r '.[] | select(.prerelease == true) | .tag_name' | head -n 1)
245+
echo "cortex.llamacpp latest release: $latest_prerelease"
201246
docker build \
202247
--build-arg REMOTE_CACHE_URL="${{ secrets.MINIO_ENDPOINT }}/vcpkg-cache" \
203248
--build-arg MINIO_ENDPOINT_URL="${{ secrets.MINIO_ENDPOINT }}" \
204249
--build-arg MINIO_ACCESS_KEY="${{ secrets.MINIO_ACCESS_KEY_ID }}" \
205250
--build-arg MINIO_SECRET_KEY="${{ secrets.MINIO_SECRET_ACCESS_KEY }}" \
251+
--build-arg CORTEX_CPP_VERSION="${latest_prerelease}" \
206252
-t menloltd/cortex:test -f docker/Dockerfile.cache .
207253
docker run -it -d -p 3928:39281 --name cortex menloltd/cortex:test
208254
sleep 20
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import pytest
2+
import sys
3+
4+
### e2e tests are expensive, have to keep engines tests in order
5+
from test_api_engine_list import TestApiEngineList
6+
from test_api_engine_install_nightly import TestApiEngineInstall
7+
from test_api_engine_get import TestApiEngineGet
8+
9+
### models, keeps in order, note that we only uninstall engine after finishing all models test
10+
from test_api_model_pull_direct_url import TestApiModelPullDirectUrl
11+
from test_api_model_start import TestApiModelStart
12+
from test_api_model_stop import TestApiModelStop
13+
from test_api_model_get import TestApiModelGet
14+
from test_api_model_list import TestApiModelList
15+
from test_api_model_update import TestApiModelUpdate
16+
from test_api_model_delete import TestApiModelDelete
17+
from test_api_model_import import TestApiModelImport
18+
from test_api_engine_uninstall import TestApiEngineUninstall
19+
20+
###
21+
from test_cli_engine_get import TestCliEngineGet
22+
from test_cli_engine_install_nightly import TestCliEngineInstall
23+
from test_cli_engine_list import TestCliEngineList
24+
from test_cli_model_delete import TestCliModelDelete
25+
from test_cli_model_pull_direct_url import TestCliModelPullDirectUrl
26+
from test_cli_server_start import TestCliServerStart
27+
from test_cortex_update import TestCortexUpdate
28+
from test_create_log_folder import TestCreateLogFolder
29+
from test_cli_model_import import TestCliModelImport
30+
from test_cli_engine_uninstall import TestCliEngineUninstall
31+
32+
if __name__ == "__main__":
33+
sys.exit(pytest.main([__file__, "-v"]))
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
import requests
3+
from test_runner import start_server, stop_server, get_latest_pre_release_tag
4+
5+
latest_pre_release_tag = get_latest_pre_release_tag("janhq", "cortex.llamacpp")
6+
7+
class TestApiEngineInstall:
8+
9+
@pytest.fixture(autouse=True)
10+
def setup_and_teardown(self):
11+
# Setup
12+
success = start_server()
13+
if not success:
14+
raise Exception("Failed to start server")
15+
16+
yield
17+
18+
# Teardown
19+
stop_server()
20+
21+
def test_engines_install_llamacpp_should_be_successful(self):
22+
response = requests.post("http://localhost:3928/v1/engines/llama-cpp/install")
23+
assert response.status_code == 200
24+
25+
def test_engines_install_llamacpp_specific_version_and_variant(self):
26+
data = {"version": latest_pre_release_tag, "variant": "linux-amd64-avx-cuda-11-7"}
27+
response = requests.post(
28+
"http://localhost:3928/v1/engines/llama-cpp/install", json=data
29+
)
30+
assert response.status_code == 200
31+
32+
def test_engines_install_llamacpp_specific_version_and_null_variant(self):
33+
data = {"version": latest_pre_release_tag}
34+
response = requests.post(
35+
"http://localhost:3928/v1/engines/llama-cpp/install", json=data
36+
)
37+
assert response.status_code == 200
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import platform
2+
import tempfile
3+
4+
import pytest
5+
import requests
6+
from test_runner import run, start_server, stop_server, get_latest_pre_release_tag
7+
8+
latest_pre_release_tag = get_latest_pre_release_tag("janhq", "cortex.llamacpp")
9+
10+
class TestCliEngineInstall:
11+
def setup_and_teardown(self):
12+
# Setup
13+
stop_server()
14+
success = start_server()
15+
if not success:
16+
raise Exception("Failed to start server")
17+
18+
yield
19+
20+
# Teardown
21+
stop_server()
22+
23+
def test_engines_install_llamacpp_should_be_successfully(self):
24+
exit_code, output, error = run(
25+
"Install Engine",
26+
["engines", "install", "llama-cpp"],
27+
timeout=None,
28+
capture=False,
29+
)
30+
response = requests.get("http://127.0.0.1:3928/v1/engines/llama-cpp")
31+
assert len(response.json()) > 0
32+
assert exit_code == 0, f"Install engine failed with error: {error}"
33+
34+
@pytest.mark.skipif(platform.system() != "Darwin", reason="macOS-specific test")
35+
def test_engines_install_onnx_on_macos_should_be_failed(self):
36+
exit_code, output, error = run(
37+
"Install Engine", ["engines", "install", "onnxruntime"]
38+
)
39+
assert "is not supported on" in output, "Should display error message"
40+
assert exit_code == 0, f"Install engine failed with error: {error}"
41+
42+
@pytest.mark.skipif(platform.system() != "Darwin", reason="macOS-specific test")
43+
def test_engines_install_onnx_on_tensorrt_should_be_failed(self):
44+
exit_code, output, error = run(
45+
"Install Engine", ["engines", "install", "tensorrt-llm"]
46+
)
47+
assert "is not supported on" in output, "Should display error message"
48+
assert exit_code == 0, f"Install engine failed with error: {error}"
49+
50+
def test_engines_should_fallback_to_download_llamacpp_engine_if_not_exists(self):
51+
exit_code, output, error = run(
52+
"Install Engine",
53+
["engines", "install", "llama-cpp", "-s", tempfile.gettempdir()],
54+
timeout=None,
55+
)
56+
# response = requests.get("http://127.0.0.1:3928/v1/engines/llama-cpp")
57+
# assert len(response.json()) > 0
58+
assert "downloaded successfully" in output
59+
assert exit_code == 0, f"Install engine failed with error: {error}"
60+
61+
def test_engines_should_not_perform_with_dummy_path(self):
62+
exit_code, output, error = run(
63+
"Install Engine",
64+
["engines", "install", "llama-cpp", "-s", "abcpod"],
65+
timeout=None,
66+
)
67+
assert "Folder does not exist" in output, "Should display error"
68+
assert exit_code == 0, f"Install engine failed with error: {error}"
69+
70+
def test_engines_install_pre_release_llamacpp(self):
71+
engine_version = latest_pre_release_tag
72+
exit_code, output, error = run(
73+
"Install Engine",
74+
["engines", "install", "llama-cpp", "-v", engine_version],
75+
timeout=None,
76+
capture=False,
77+
)
78+
response = requests.get("http://127.0.0.1:3928/v1/engines/llama-cpp")
79+
assert len(response.json()) > 0
80+
is_engine_version_exist = False
81+
for item in response.json():
82+
# Check if 'version' key exists and matches target
83+
if "version" in item and item["version"] == engine_version:
84+
is_engine_version_exist = True
85+
break
86+
87+
# loop through all the installed response, expect we find
88+
assert is_engine_version_exist, f"Engine version {engine_version} is not found"
89+
assert exit_code == 0, f"Install engine failed with error: {error}"

engine/e2e-test/test_runner.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import subprocess
77
import threading
88
import time
9+
import requests
910
from typing import List
1011

1112
import websockets
@@ -187,3 +188,36 @@ async def receive_until_success():
187188

188189
except asyncio.TimeoutError:
189190
raise TimeoutError("Timeout waiting for DownloadSuccess event")
191+
192+
193+
def get_latest_pre_release_tag(repo_owner, repo_name):
194+
# URL for GitHub API to fetch all releases of the repository
195+
url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases"
196+
197+
# Headers to specify the API version
198+
headers = {
199+
"Accept": "application/vnd.github.v3+json"
200+
}
201+
202+
# Send a GET request to the GitHub API
203+
response = requests.get(url, headers=headers)
204+
205+
# Check the response status; raise an error if the request failed
206+
if response.status_code != 200:
207+
raise Exception(f"Failed to fetch releases: {response.status_code}, {response.text}")
208+
209+
# Parse the JSON response into a list of releases
210+
releases = response.json()
211+
212+
# Filter the releases to include only pre-releases
213+
pre_releases = [release for release in releases if release.get("prerelease")]
214+
215+
# If no pre-releases are found, raise an exception
216+
if not pre_releases:
217+
raise Exception("No pre-releases found")
218+
219+
# Sort the pre-releases by creation date, newest first
220+
pre_releases.sort(key=lambda x: x["created_at"], reverse=True)
221+
222+
# Return the tag name of the latest pre-release
223+
return pre_releases[0]["tag_name"]

0 commit comments

Comments
 (0)