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
68 changes: 45 additions & 23 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,60 @@ name: Rust

on:
push:
branches: [ "main" ]
paths: ['src/**', 'tests/**', 'third-party/**', 'Cargo.toml', 'build.rs', 'init.sh', '.github/workflows/rust.yml']
branches: ["main"]
paths:
[
"src/**",
"tests/**",
"third-party/**",
"Cargo.toml",
"build.rs",
"init.sh",
".github/workflows/rust.yml",
]
pull_request:
branches: [ "main" ]
paths: ['src/**', 'tests/**', 'third-party/**', 'Cargo.toml', 'build.rs', 'init.sh', '.github/workflows/rust.yml']
branches: ["main"]
paths:
[
"src/**",
"tests/**",
"third-party/**",
"Cargo.toml",
"build.rs",
"init.sh",
".github/workflows/rust.yml",
]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
with:
submodules: 'true'
- uses: actions/checkout@v6
with:
submodules: "true"

# Step to install OCaml compiler and dependencies
- name: Install OCaml
run: |
sudo apt-get update
sudo apt-get install -y ocaml opam
opam init --disable-sandboxing -y
eval $(opam env)
- name: Install watchman
run: |
curl -L --output watchman.deb https://github.com/mszabo-wikia/watchman/releases/download/v0.1.3-testing/watchman_ubuntu24.04_v0.1.3-testing.deb
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I have the only prepackaged watchman for Noble...

echo "d88fa6b2b5040b6cf3f3c26b3c0eb21d4273c9591205657bb4363b83f8483944 watchman.deb" | sha256sum -c
sudo apt install -y ./watchman.deb

# Step to install OCaml compiler and dependencies
- name: Install OCaml
run: |
sudo apt-get update
sudo apt-get install -y ocaml opam watchman
opam init --disable-sandboxing -y
eval $(opam env)

- name: Init repository
run: ./init.sh
- name: Build & Run tests
run: |
cargo run --release --bin hakana test tests
- name: Init repository
run: ./init.sh
- name: Build & Run tests
run: |
cargo run --release --bin hakana test tests

# run Rust unit tests
cargo test --release --workspace
# run Rust unit tests
cargo test --release --workspace
8 changes: 8 additions & 0 deletions src/cli/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,13 @@ pub async fn init(
Logger::DevNull
}
}
Some(("server", sub_matches)) => {
if sub_matches.is_present("debug") {
Logger::CommandLine(Verbosity::Debugging)
} else {
Logger::CommandLine(Verbosity::Simple)
}
}
Some((_, sub_matches)) => {
if sub_matches.is_present("debug") {
Logger::CommandLine(Verbosity::Debugging)
Expand Down Expand Up @@ -2714,6 +2721,7 @@ async fn do_server(
config_path: Some(config_path),
plugins,
header: header.to_string(),
chaos_monkey: None,
};

match Server::new(server_config, Arc::new(logger)) {
Expand Down
11 changes: 4 additions & 7 deletions src/language_server/server_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,8 @@ impl ServerConnection {
);

let log_path = std::env::temp_dir().join("hakana-server.log");
let log_file = std::fs::File::create(&log_path).ok();
let log_file2 = std::fs::OpenOptions::new()
.append(true)
.open(&log_path)
.ok();
let stdout = std::fs::File::create(&log_path)?;
let stderr = stdout.try_clone()?;

eprintln!("Server log file: {}", log_path.display());

Expand All @@ -112,8 +109,8 @@ impl ServerConnection {
.arg(project_root)
.current_dir(project_root)
.stdin(Stdio::null())
.stdout(log_file.map(Stdio::from).unwrap_or(Stdio::null()))
.stderr(log_file2.map(Stdio::from).unwrap_or(Stdio::null()))
.stdout(Stdio::from(stdout))
.stderr(Stdio::from(stderr))
.spawn()
.map_err(|e| {
io::Error::new(
Expand Down
14 changes: 5 additions & 9 deletions src/server/handler.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
//! Request handlers for the hakana server.

use crate::{ServerConfig, ServerState};
use hakana_analyzer::config::Config;
use hakana_code_info::analysis_result::{self, AnalysisResult};
use hakana_code_info::data_flow::graph::{GraphKind, WholeProgramKind};
use hakana_code_info::analysis_result::AnalysisResult;
use hakana_logger::Logger;
use hakana_orchestrator::SuccessfulScanData;
use hakana_orchestrator::file::FileStatus;
use hakana_protocol::{
AckResponse, AnalyzeRequest, AnalyzeResponse, ErrorCode, ErrorResponse, FileChange,
AckResponse,
FindReferencesRequest, FindReferencesResponse, FindSymbolReferencesRequest,
FindSymbolReferencesResponse, GotoDefinitionRequest, GotoDefinitionResponse, Message,
ProtocolIssue, ReferenceLocation, SecurityCheckRequest, SecurityCheckResponse, StatusResponse,
ProtocolIssue, ReferenceLocation, StatusResponse,
};
use hakana_protocol::{FileChangeStatus, GetIssuesResponse};
use hakana_str::Interner;
use rustc_hash::{FxHashMap, FxHashSet};
use std::path::Path;
use hakana_protocol::GetIssuesResponse;
use rustc_hash::FxHashMap;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use tokio::sync::mpsc::Sender;
Expand Down
122 changes: 121 additions & 1 deletion src/server/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct ServerConfig {
pub config_path: Option<String>,
pub plugins: Vec<Arc<dyn CustomHook>>,
pub header: String,
pub chaos_monkey: Option<Arc<dyn Fn() + Send + Sync>>,
}

impl ServerConfig {
Expand All @@ -39,6 +40,7 @@ impl ServerConfig {
config_path: None,
plugins: Vec::new(),
header: String::new(),
chaos_monkey: None,
}
}
}
Expand Down Expand Up @@ -443,8 +445,126 @@ fn run_analysis(
previous_scan_data,
previous_analysis_result,
changes,
|| {},
|| {
if let Some(f) = &config.chaos_monkey {
f();
}
},
Some(progress),
)
.map_err(|e| e.to_string())
}

#[cfg(test)]
mod tests {
use hakana_logger::Logger;
use hakana_protocol::{ClientSocket, GetIssuesRequest, Message};
use std::{
path::{Path, PathBuf},
sync::{Arc, atomic::AtomicBool},
};
use tokio::fs;

use crate::{Server, ServerConfig, watchman};

#[tokio::test]
async fn handles_file_changes_during_analysis() -> std::io::Result<()> {
let tmp = tempfile::Builder::new()
.prefix("hakana-test")
.tempdir()
.expect("failed to create temp dir");
let hack_file = tmp.path().join("index.hack");
let config_path = tmp.path().join("hakana.json");
fs::write(hack_file.clone(), "function main(): void {}")
.await
.expect("failed to create test file");
fs::write(config_path.clone(), "{}")
.await
.expect("failed to create test file");

eprintln!("{:?}", hack_file);

let did_mutate = AtomicBool::new(false);

let server_config = ServerConfig {
root_dir: tmp.path().to_str().unwrap().to_string(),
threads: 2,
config_path: Some(config_path.to_str().unwrap().to_string()),
plugins: vec![],
header: "".to_string(),
chaos_monkey: Some(Arc::new(move || {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nice

// Simulate a file change between the first scan and first analysis
if !did_mutate.load(std::sync::atomic::Ordering::Relaxed) {
eprintln!("editing file before analysis");
did_mutate.store(true, std::sync::atomic::Ordering::Relaxed);
std::fs::write(hack_file.clone(), "function foo(): void {}")
.expect("failed to mutate file before analysis");
}
})),
};

let watchman_clock = watchman::get_clock(Path::new(&server_config.root_dir)).await?;
let mut watchman_handle = watchman::start_subscription(
PathBuf::from(&server_config.root_dir),
vec![],
watchman_clock,
server_config.config_path.as_ref().map(&PathBuf::from),
);

let mut server = Server::new(
server_config,
Arc::new(Logger::CommandLine(hakana_logger::Verbosity::Debugging)),
)
.expect("failed to create server");

let socket_path = server.socket_path().clone();
let shutdown_tx = server.shutdown_tx.clone();

let server_task =
tokio::spawn(async move { server.run().await.expect("failed to run server") });

// Wait for Watchman to send a notification (which should also have notified the server)
watchman_handle.recv().await;

let mut client = ClientSocket::connect(&socket_path)
.await
.expect("failed to connect");

let request = Message::GetIssues(GetIssuesRequest {
filter: None,
find_unused_expressions: false,
find_unused_definitions: false,
block_until_next_analysis: false,
send_progress_report: false,
});

let response = client
.request(&request)
.await
.expect("Failed to send request");

if let Message::GetIssuesResult(result) = response {
eprintln!("{:?}", result.issues);
assert!(
result.analysis_complete,
"Analysis should be complete after server is ready"
);
assert_eq!(0, result.issues.len(), "there should be no issues reported");
assert_eq!(
1, result.files_analyzed,
"one file should have been analyzed"
)
} else {
panic!("Expected GetIssuesResult, got {:?}", response);
}

shutdown_tx
.send(true)
.await
.expect("failed to shutdown server");

server_task.await.expect("failed to join server task");

Ok(())
}
}