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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ test: lint
cargo test --manifest-path src-tauri/Cargo.toml --no-default-features --features test-tauri -- --test-threads=1
cargo test --manifest-path src-tauri/plugins/tauri-plugin-hardware/Cargo.toml
cargo test --manifest-path src-tauri/plugins/tauri-plugin-llamacpp/Cargo.toml
cargo test --manifest-path src-tauri/utils/Cargo.toml

# Builds and publishes the app
build-and-publish: install-and-build
Expand Down
54 changes: 49 additions & 5 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,56 @@ description = "Run linting (matches Makefile)"
depends = ["build-extensions"]
run = "yarn lint"

[tasks.test]
description = "Run test suite (matches Makefile)"
depends = ["lint"]
# ============================================================================
# RUST TEST COMPONENTS
# ============================================================================

[tasks.test-rust-main]
description = "Test main src-tauri package"
run = "cargo test --manifest-path src-tauri/Cargo.toml --no-default-features --features test-tauri -- --test-threads=1"

[tasks.test-rust-hardware]
description = "Test hardware plugin"
run = "cargo test --manifest-path src-tauri/plugins/tauri-plugin-hardware/Cargo.toml"

[tasks.test-rust-llamacpp]
description = "Test llamacpp plugin"
run = "cargo test --manifest-path src-tauri/plugins/tauri-plugin-llamacpp/Cargo.toml"

[tasks.test-rust-utils]
description = "Test utils package"
run = "cargo test --manifest-path src-tauri/utils/Cargo.toml"

[tasks.test-rust]
description = "Run all Rust tests"
depends = ["test-rust-main", "test-rust-hardware", "test-rust-llamacpp", "test-rust-utils"]

# ============================================================================
# JS TEST COMPONENTS
# ============================================================================

[tasks.test-js-setup]
description = "Setup for JS tests"
run = [
"yarn download:bin",
"yarn download:lib",
"yarn copy:assets:tauri",
"yarn build:icon"
]

[tasks.test-js]
description = "Run JS tests"
depends = ["test-js-setup"]
run = "yarn test"

# ============================================================================
# COMBINED TEST TASKS
# ============================================================================

[tasks.test]
description = "Run complete test suite (matches Makefile)"
depends = ["lint", "test-js", "test-rust"]

# ============================================================================
# PARALLEL-FRIENDLY QUALITY ASSURANCE TASKS
# ============================================================================
Expand All @@ -155,8 +200,7 @@ hide = true

[tasks.test-only]
description = "Run tests only (parallel-friendly)"
depends = ["build-extensions"]
run = "yarn test"
depends = ["build-extensions", "test-js", "test-rust"]
hide = true

[tasks.qa-parallel]
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "jan-app",
"private": true,
"type": "module",
"workspaces": {
"packages": [
"core",
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/plugins/tauri-plugin-llamacpp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ windows-sys = { version = "0.60.2", features = ["Win32_Storage_FileSystem"] }
[target.'cfg(unix)'.dependencies]
nix = { version = "=0.30.1", features = ["signal", "process"] }

[dev-dependencies]
tempfile = "3.0"

[build-dependencies]
tauri-plugin = { version = "2.3.1", features = ["build"] }
183 changes: 183 additions & 0 deletions src-tauri/plugins/tauri-plugin-llamacpp/src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,186 @@ fn parse_memory_value(mem_str: &str) -> ServerResult<i32> {
.into()
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_is_memory_pattern_valid() {
assert!(is_memory_pattern("8128 MiB, 8128 MiB free"));
assert!(is_memory_pattern("1024 MiB, 512 MiB free"));
assert!(is_memory_pattern("16384 MiB, 12000 MiB free"));
assert!(is_memory_pattern("0 MiB, 0 MiB free"));
}

#[test]
fn test_is_memory_pattern_invalid() {
assert!(!is_memory_pattern("8128 MB, 8128 MB free")); // Wrong unit
assert!(!is_memory_pattern("8128 MiB 8128 MiB free")); // Missing comma
assert!(!is_memory_pattern("8128 MiB, 8128 MiB used")); // Wrong second part
assert!(!is_memory_pattern("not_a_number MiB, 8128 MiB free")); // Invalid number
assert!(!is_memory_pattern("8128 MiB")); // Missing second part
assert!(!is_memory_pattern("")); // Empty string
assert!(!is_memory_pattern("8128 MiB, free")); // Missing number in second part
}

#[test]
fn test_find_memory_pattern() {
let text = "Intel(R) Arc(tm) A750 Graphics (DG2) (8128 MiB, 4096 MiB free)";
let result = find_memory_pattern(text);
assert!(result.is_some());
let (start_idx, content) = result.unwrap();
assert!(start_idx > 0);
assert_eq!(content, "8128 MiB, 4096 MiB free");
}

#[test]
fn test_find_memory_pattern_multiple_parentheses() {
let text = "Device (test) with (1024 MiB, 512 MiB free) and (2048 MiB, 1024 MiB free)";
let result = find_memory_pattern(text);
assert!(result.is_some());
let (_, content) = result.unwrap();
// Should return the LAST valid memory pattern
assert_eq!(content, "2048 MiB, 1024 MiB free");
}

#[test]
fn test_find_memory_pattern_no_match() {
let text = "No memory info here";
assert!(find_memory_pattern(text).is_none());

let text_with_invalid = "Some text (invalid memory info) here";
assert!(find_memory_pattern(text_with_invalid).is_none());
}

#[test]
fn test_parse_memory_value() {
assert_eq!(parse_memory_value("8128 MiB").unwrap(), 8128);
assert_eq!(parse_memory_value("7721 MiB free").unwrap(), 7721);
assert_eq!(parse_memory_value("0 MiB").unwrap(), 0);
assert_eq!(parse_memory_value("24576 MiB").unwrap(), 24576);
}

#[test]
fn test_parse_memory_value_invalid() {
assert!(parse_memory_value("").is_err());
assert!(parse_memory_value("not_a_number MiB").is_err());
assert!(parse_memory_value(" ").is_err());
}

#[test]
fn test_parse_device_line_vulkan() {
let line = "Vulkan0: Intel(R) Arc(tm) A750 Graphics (DG2) (8128 MiB, 8128 MiB free)";
let result = parse_device_line(line).unwrap();
assert!(result.is_some());
let device = result.unwrap();
assert_eq!(device.id, "Vulkan0");
assert_eq!(device.name, "Intel(R) Arc(tm) A750 Graphics (DG2)");
assert_eq!(device.mem, 8128);
assert_eq!(device.free, 8128);
}

#[test]
fn test_parse_device_line_cuda() {
let line = "CUDA0: NVIDIA GeForce RTX 4090 (24576 MiB, 24000 MiB free)";
let result = parse_device_line(line).unwrap();
assert!(result.is_some());
let device = result.unwrap();
assert_eq!(device.id, "CUDA0");
assert_eq!(device.name, "NVIDIA GeForce RTX 4090");
assert_eq!(device.mem, 24576);
assert_eq!(device.free, 24000);
}

#[test]
fn test_parse_device_line_sycl() {
let line = "SYCL0: Intel(R) Arc(TM) A750 Graphics (8000 MiB, 7721 MiB free)";
let result = parse_device_line(line).unwrap();
assert!(result.is_some());
let device = result.unwrap();
assert_eq!(device.id, "SYCL0");
assert_eq!(device.name, "Intel(R) Arc(TM) A750 Graphics");
assert_eq!(device.mem, 8000);
assert_eq!(device.free, 7721);
}

#[test]
fn test_parse_device_line_malformed() {
// Missing colon
let result = parse_device_line("Vulkan0 Intel Graphics (8128 MiB, 8128 MiB free)").unwrap();
assert!(result.is_none());

// Missing memory info
let result = parse_device_line("Vulkan0: Intel Graphics").unwrap();
assert!(result.is_none());

// Invalid memory format
let result = parse_device_line("Vulkan0: Intel Graphics (invalid memory)").unwrap();
assert!(result.is_none());
}

#[test]
fn test_parse_device_output_valid() {
let output = r#"
Some header text
Available devices:
Vulkan0: Intel(R) Arc(tm) A750 Graphics (DG2) (8128 MiB, 8128 MiB free)
CUDA0: NVIDIA GeForce RTX 4090 (24576 MiB, 24000 MiB free)

SYCL0: Intel(R) Arc(TM) A750 Graphics (8000 MiB, 7721 MiB free)
Some footer text
"#;

let result = parse_device_output(output).unwrap();
assert_eq!(result.len(), 3);

assert_eq!(result[0].id, "Vulkan0");
assert_eq!(result[0].name, "Intel(R) Arc(tm) A750 Graphics (DG2)");
assert_eq!(result[0].mem, 8128);

assert_eq!(result[1].id, "CUDA0");
assert_eq!(result[1].name, "NVIDIA GeForce RTX 4090");
assert_eq!(result[1].mem, 24576);

assert_eq!(result[2].id, "SYCL0");
assert_eq!(result[2].name, "Intel(R) Arc(TM) A750 Graphics");
assert_eq!(result[2].mem, 8000);
}

#[test]
fn test_parse_device_output_no_devices_section() {
let output = "Some output without Available devices section";
let result = parse_device_output(output);
assert!(result.is_err());
}

#[test]
fn test_parse_device_output_empty_devices() {
let output = r#"
Some header text
Available devices:

Some footer text
"#;
let result = parse_device_output(output).unwrap();
assert_eq!(result.len(), 0);
}

#[test]
fn test_parse_device_output_mixed_valid_invalid() {
let output = r#"
Available devices:
Vulkan0: Intel(R) Arc(tm) A750 Graphics (DG2) (8128 MiB, 8128 MiB free)
InvalidLine: No memory info
CUDA0: NVIDIA GeForce RTX 4090 (24576 MiB, 24000 MiB free)
AnotherInvalid
"#;

let result = parse_device_output(output).unwrap();
assert_eq!(result.len(), 2); // Only valid lines should be parsed

assert_eq!(result[0].id, "Vulkan0");
assert_eq!(result[1].id, "CUDA0");
}
}
Loading
Loading