Skip to content

Commit 5d6b64f

Browse files
committed
add make lint and make test
Signed-off-by: kerthcet <kerthcet@gmail.com>
1 parent 69b2b78 commit 5d6b64f

9 files changed

Lines changed: 145 additions & 28 deletions

File tree

.github/workflows/kube-workflow-init.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ jobs:
77
init:
88
uses: kerthcet/github-workflow-as-kube/.github/workflows/workflow-as-kubernetes-init.yaml@main
99
secrets:
10-
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}
10+
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}

.github/workflows/rust-ci.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Rust CI
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- synchronize
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
lint:
14+
name: Lint
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Install Rust toolchain
20+
uses: actions-rust-lang/setup-rust-toolchain@v1
21+
with:
22+
toolchain: stable
23+
components: rustfmt, clippy
24+
25+
- name: Run lint
26+
run: make lint
27+
28+
test:
29+
name: Test
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- name: Install Rust toolchain
35+
uses: actions-rust-lang/setup-rust-toolchain@v1
36+
with:
37+
toolchain: stable
38+
39+
- name: Run tests
40+
run: make test

Makefile

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
RUFF := .venv/bin/ruff
2+
13
.PHONY: help build install dev test clean daemon-build daemon-release
24

35
help:
@@ -20,8 +22,15 @@ release:
2022
dev:
2123
maturin develop -m server/Cargo.toml
2224

23-
test:
24-
pytest tests/
25+
test: lint
26+
@echo "Running Rust tests (daemon)..."
27+
cargo test --package sandd
28+
@echo ""
29+
@echo "Running Rust tests (server protocol)..."
30+
cargo test --package sandbox-server --lib
31+
@echo ""
32+
@echo "Running Python tests..."
33+
pytest python/tests/
2534

2635
daemon-build:
2736
cargo build --package sandd
@@ -36,3 +45,13 @@ clean:
3645
rm -rf target/
3746
rm -rf python/sandd.egg-info/
3847
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
48+
49+
.PHONY: lint
50+
lint: $(RUFF)
51+
$(RUFF) check .
52+
53+
$(RUFF):
54+
@echo "Installing ruff..."
55+
@python3 -m venv .venv || true
56+
@.venv/bin/pip install --quiet ruff
57+
@echo "Ruff installed successfully"

examples/agent_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def main():
3636

3737
# Get server stats
3838
stats = server.get_stats()
39-
print(f"Server stats:")
39+
print("Server stats:")
4040
print(f" Total daemons: {stats.total_daemons}")
4141
print(f" By platform: {stats.by_platform}")
4242
print(f" Oldest connection: {stats.oldest_connection_secs}s")

python/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Python tests for SandD

python/tests/test_server.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Unit tests for SandD Server"""
2+
import pytest
3+
from sandd import Server, ServerStats
4+
5+
6+
def test_server_initialization():
7+
"""Test server can be initialized with default parameters"""
8+
server = Server()
9+
assert server.address == "0.0.0.0:8765"
10+
assert server.daemon_count() == 0
11+
12+
13+
def test_server_custom_address():
14+
"""Test server can be initialized with custom host and port"""
15+
server = Server(host="127.0.0.1", port=9999)
16+
assert server.address == "127.0.0.1:9999"
17+
18+
19+
def test_list_daemons_empty():
20+
"""Test listing daemons returns empty list when none connected"""
21+
server = Server()
22+
daemons = server.list_daemons()
23+
assert isinstance(daemons, list)
24+
assert len(daemons) == 0
25+
26+
27+
def test_execute_command_daemon_not_found():
28+
"""Test executing command on non-existent daemon raises ValueError"""
29+
server = Server()
30+
with pytest.raises(ValueError, match="not found"):
31+
server.execute_command("non-existent-daemon", "echo test")
32+
33+
34+
def test_wait_for_daemon_timeout():
35+
"""Test wait_for_daemon returns False on timeout"""
36+
server = Server()
37+
result = server.wait_for_daemon("non-existent", timeout=0.1, poll_interval=0.05)
38+
assert result is False
39+
40+
41+
def test_server_repr():
42+
"""Test server string representation"""
43+
server = Server(host="localhost", port=8080)
44+
repr_str = repr(server)
45+
assert "localhost:8080" in repr_str
46+
assert "daemons=0" in repr_str
47+
48+
49+
def test_get_stats():
50+
"""Test getting server statistics"""
51+
server = Server()
52+
stats = server.get_stats()
53+
assert isinstance(stats, ServerStats)
54+
assert stats.total_daemons == 0
55+
assert isinstance(stats.by_platform, dict)

sandd/src/main.rs

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,7 @@ async fn main() -> Result<()> {
6161
Err(e) => error!("Connection error: {}", e),
6262
}
6363

64-
warn!(
65-
"Reconnecting in {} seconds...",
66-
args.reconnect_interval
67-
);
64+
warn!("Reconnecting in {} seconds...", args.reconnect_interval);
6865
tokio::time::sleep(Duration::from_secs(args.reconnect_interval)).await;
6966
}
7067
}
@@ -81,7 +78,7 @@ async fn connect_and_serve(
8178
let mut request = server_url.into_client_request()?;
8279
request.headers_mut().insert(
8380
"Sec-WebSocket-Protocol",
84-
tokio_tungstenite::tungstenite::http::HeaderValue::from_static("sandd.v1")
81+
tokio_tungstenite::tungstenite::http::HeaderValue::from_static("sandd.v1"),
8582
);
8683

8784
let (ws_stream, response) = match tokio_tungstenite::connect_async(request).await {
@@ -184,13 +181,7 @@ async fn connect_and_serve(
184181
};
185182

186183
// Handle message inline
187-
if let Err(e) = handle_message(
188-
message,
189-
ws_tx_clone.clone(),
190-
executor.clone(),
191-
)
192-
.await
193-
{
184+
if let Err(e) = handle_message(message, ws_tx_clone.clone(), executor.clone()).await {
194185
error!("Error handling message: {}", e);
195186
}
196187
}
@@ -245,13 +236,11 @@ where
245236

246237
let json = serde_json::to_string(&response)?;
247238
let mut tx = ws_tx.lock().await;
248-
tx.send(WsMessage::Text(json)).await.map_err(|e| anyhow::anyhow!("{}", e))?;
239+
tx.send(WsMessage::Text(json)).await?
249240
} else {
250241
// Normal shell execution
251242
debug!("Executing command: {}", command);
252-
let result = executor
253-
.execute(&command, timeout_secs, env, cwd)
254-
.await;
243+
let result = executor.execute(&command, timeout_secs, env, cwd).await;
255244

256245
let response = match result {
257246
Ok(output) => Message::CommandOutput {
@@ -269,7 +258,9 @@ where
269258

270259
let json = serde_json::to_string(&response)?;
271260
let mut tx = ws_tx.lock().await;
272-
tx.send(WsMessage::Text(json)).await.map_err(|e| anyhow::anyhow!("{}", e))?;
261+
tx.send(WsMessage::Text(json))
262+
.await
263+
.map_err(|e| anyhow::anyhow!("{}", e))?;
273264
}
274265
}
275266

@@ -279,7 +270,10 @@ where
279270
cols: _,
280271
term: _,
281272
} => {
282-
debug!("Starting shell session: {} (not implemented in MVP)", request_id);
273+
debug!(
274+
"Starting shell session: {} (not implemented in MVP)",
275+
request_id
276+
);
283277

284278
// TODO: Shell functionality disabled for MVP due to PtySystem Sync issues
285279
let response = Message::ShellStarted {
@@ -290,10 +284,15 @@ where
290284

291285
let json = serde_json::to_string(&response)?;
292286
let mut tx = ws_tx.lock().await;
293-
tx.send(WsMessage::Text(json)).await.map_err(|e| anyhow::anyhow!("{}", e))?;
287+
tx.send(WsMessage::Text(json))
288+
.await
289+
.map_err(|e| anyhow::anyhow!("{}", e))?;
294290
}
295291

296-
Message::ShellInput { request_id: _, data: _ } => {
292+
Message::ShellInput {
293+
request_id: _,
294+
data: _,
295+
} => {
297296
debug!("Shell input (not implemented)");
298297
// TODO: Shell functionality disabled for MVP
299298
}
@@ -324,7 +323,11 @@ where
324323
offset,
325324
} => {
326325
// In a full implementation, write chunks to file
327-
debug!("Received file chunk: {} bytes at offset {}", data.len(), offset);
326+
debug!(
327+
"Received file chunk: {} bytes at offset {}",
328+
data.len(),
329+
offset
330+
);
328331
}
329332

330333
Message::FileDownloadStart { request_id, path } => {

server/src/protocol.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use serde::{Deserialize, Serialize};
2-
use uuid::Uuid;
32

43
/// Protocol messages exchanged between agent and daemon
54
#[derive(Debug, Clone, Serialize, Deserialize)]

server/src/server.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::protocol::Message;
22
use crate::registry::{CommandResult, DaemonConnection, DaemonRegistry};
3-
use anyhow::{anyhow, Context, Result};
3+
use anyhow::{Context, Result};
44
use axum::{
55
extract::{
66
ws::{WebSocket, WebSocketUpgrade},
@@ -14,7 +14,7 @@ use axum::{
1414
use futures_util::{SinkExt, StreamExt};
1515
use std::sync::Arc;
1616
use std::time::Duration;
17-
use tokio::sync::{mpsc, oneshot};
17+
use tokio::sync::mpsc;
1818
use tracing::{debug, error, info, warn};
1919

2020
pub struct SandboxServer {

0 commit comments

Comments
 (0)