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
31 changes: 30 additions & 1 deletion .github/workflows/main-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
ci:
name: OASIS_OS CI
runs-on: self-hosted
timeout-minutes: 30
timeout-minutes: 60
steps:
- name: Pre-checkout cleanup
run: |
Expand Down Expand Up @@ -132,6 +132,35 @@ jobs:
echo "| Failed | $FAIL |" >> $GITHUB_STEP_SUMMARY
echo "| Total | $TOTAL |" >> $GITHUB_STEP_SUMMARY

# -- Coverage Metrics ----------------------------------------------------
- name: Coverage (cargo-llvm-cov)
continue-on-error: true
run: |
docker compose --profile ci run --rm rust-ci bash -c "
rustup component add llvm-tools-preview 2>/dev/null || true
cargo install cargo-llvm-cov --locked 2>/dev/null || true
if command -v cargo-llvm-cov &>/dev/null; then
cargo llvm-cov --workspace --no-fail-fast \
--ignore-filename-regex '(tests?\.rs|benches?\.rs|main\.rs)' \
2>&1 | tee /app/coverage_output.txt
else
echo 'cargo-llvm-cov not available, skipping coverage'
fi
"

- name: Coverage summary
if: always()
run: |
if [ ! -f coverage_output.txt ]; then
echo "### Coverage" >> $GITHUB_STEP_SUMMARY
echo "Coverage step was skipped or unavailable." >> $GITHUB_STEP_SUMMARY
exit 0
fi
echo "### Coverage" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
tail -20 coverage_output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

# -- PSP Backend Build -------------------------------------------------
- name: Setup PSP SDK
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
name: OASIS_OS CI
needs: fork-guard
runs-on: self-hosted
timeout-minutes: 30
timeout-minutes: 60
steps:
- name: Pre-checkout cleanup
run: |
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ rustls = { version = "0.23", default-features = false, features = ["ring", "logg
webpki-roots = "1.0"
rustls-pki-types = "1"

# Testing
proptest = "1"
criterion = { version = "0.5", features = ["html_reports"] }

# Internal crates
oasis-types = { path = "crates/oasis-types" }
oasis-vfs = { path = "crates/oasis-vfs" }
Expand Down
1 change: 1 addition & 0 deletions crates/oasis-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition.workspace = true
license.workspace = true
repository.workspace = true
authors.workspace = true
documentation = "https://docs.rs/oasis-app"

[[bin]]
name = "oasis-app"
Expand Down
123 changes: 123 additions & 0 deletions crates/oasis-app/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,4 +589,127 @@ mod tests {
"Already connected. Disconnect first."
);
}

// -- Additional command handler tests --

#[test]
fn process_listen_already_running() {
let mut state = make_test_state();
// Start a listener first.
let port = 0; // Cannot actually bind, but simulate the state.
let cfg = oasis_core::net::ListenerConfig {
port: 19999,
psk: String::new(),
max_connections: 1,
..oasis_core::net::ListenerConfig::default()
};
state.listener = Some(oasis_core::net::RemoteListener::new(cfg));
let result =
process_command_output(Ok(CommandOutput::ListenToggle { port: 8080 }), &mut state);
assert!(result.is_none());
assert!(state.output_lines[0].contains("already running"));
}

#[test]
fn process_ftp_already_running() {
let mut state = make_test_state();
state.ftp_server = Some(oasis_core::transfer::FtpServer::new(19000));
let result = process_command_output(Ok(CommandOutput::FtpToggle { port: 21 }), &mut state);
assert!(result.is_none());
assert!(state.output_lines[0].contains("already running"));
}

#[test]
fn process_multi_empty_list() {
let mut state = make_test_state();
let result = process_command_output(Ok(CommandOutput::Multi(vec![])), &mut state);
assert!(result.is_none());
assert!(state.output_lines.is_empty());
}

#[test]
fn process_multi_preserves_order() {
let mut state = make_test_state();
let result = process_command_output(
Ok(CommandOutput::Multi(vec![
CommandOutput::Text("alpha".to_string()),
CommandOutput::Text("beta".to_string()),
CommandOutput::Text("gamma".to_string()),
])),
&mut state,
);
assert!(result.is_none());
assert_eq!(state.output_lines.len(), 3);
assert_eq!(state.output_lines[0], "alpha");
assert_eq!(state.output_lines[1], "beta");
assert_eq!(state.output_lines[2], "gamma");
}

#[test]
fn process_multi_last_skin_swap_wins() {
let mut state = make_test_state();
let result = process_command_output(
Ok(CommandOutput::Multi(vec![
CommandOutput::SkinSwap {
name: "first".to_string(),
},
CommandOutput::SkinSwap {
name: "second".to_string(),
},
])),
&mut state,
);
assert_eq!(result, Some("second".to_string()));
}

#[test]
fn process_table_empty_rows() {
let mut state = make_test_state();
let result = process_command_output(
Ok(CommandOutput::Table {
headers: vec!["Col1".into(), "Col2".into()],
rows: vec![],
}),
&mut state,
);
assert!(result.is_none());
assert_eq!(state.output_lines.len(), 1);
assert_eq!(state.output_lines[0], "Col1 | Col2");
}

#[test]
fn process_text_multiline() {
let mut state = make_test_state();
let text = "line1\nline2\nline3\nline4";
let result = process_command_output(Ok(CommandOutput::Text(text.to_string())), &mut state);
assert!(result.is_none());
assert_eq!(state.output_lines.len(), 4);
}

#[test]
fn process_clear_empties_all() {
let mut state = make_test_state();
state.output_lines = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let result = process_command_output(Ok(CommandOutput::Clear), &mut state);
assert!(result.is_none());
assert!(state.output_lines.is_empty());
}

#[test]
fn trim_output_single_excess() {
let count = terminal_sdi::MAX_OUTPUT_LINES + 1;
let mut lines: Vec<String> = (0..count).map(|i| format!("line{i}")).collect();
trim_output(&mut lines);
assert_eq!(lines.len(), terminal_sdi::MAX_OUTPUT_LINES);
assert_eq!(lines[0], "line1");
}

#[test]
fn process_error_format() {
let mut state = make_test_state();
let err = oasis_core::error::OasisError::Vfs("file not found".into());
process_command_output(Err(err), &mut state);
assert!(state.output_lines[0].contains("error:"));
assert!(state.output_lines[0].contains("file not found"));
}
}
Loading