refactor: remove log dependency, replace with custom macros
- Added custom logging macros (log_error!, log_warn!, log_info!, log_debug!) - Implemented LogLevel enum and LOG_LEVEL static for runtime level control - Exported macros with #[macro_export] for use across all modules - Replaced all log:: calls in websocket_client.rs, health.rs, and version.rs - Removed obsolete test code referencing old log crate - Removed log dependency from Cargo.toml - Fixed useless comparison warning in test - Binary size: still 1.5M (no change - log was lightweight) - Tests: 65 passing, 0 warnings This completes the removal of 9 dependencies (thiserror, hostname, chrono, base64, rand, ureq, tiny_http, anyhow, log). Dependency count: 19 → 10. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0daaf9ed68
commit
71267870b0
6 changed files with 45 additions and 84 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1085,7 +1085,6 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
|
||||||
"prost",
|
"prost",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
log = { version = "0.4", features = ["std"] }
|
|
||||||
clap = { version = "4.0", features = ["derive", "env"] }
|
clap = { version = "4.0", features = ["derive", "env"] }
|
||||||
prost = "0.13"
|
prost = "0.13"
|
||||||
prost-types = "0.13"
|
prost-types = "0.13"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use log::info;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
@ -19,7 +18,7 @@ pub async fn start_health_server(port: u16) -> Result<()> {
|
||||||
let start_time = Arc::new(Instant::now());
|
let start_time = Arc::new(Instant::now());
|
||||||
let addr = format!("0.0.0.0:{}", port);
|
let addr = format!("0.0.0.0:{}", port);
|
||||||
|
|
||||||
info!("Starting health endpoint on {}", addr);
|
crate::log_info!("Starting health endpoint on {}", addr);
|
||||||
let listener = TcpListener::bind(&addr).await?;
|
let listener = TcpListener::bind(&addr).await?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -68,7 +67,7 @@ pub async fn start_health_server(port: u16) -> Result<()> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Failed to accept health check connection: {}", e);
|
crate::log_warn!("Failed to accept health check connection: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
src/main.rs
62
src/main.rs
|
|
@ -12,10 +12,10 @@ use tokio::time::sleep;
|
||||||
use websocket_client::AgentClient;
|
use websocket_client::AgentClient;
|
||||||
|
|
||||||
// Log levels
|
// Log levels
|
||||||
static LOG_LEVEL: std::sync::OnceLock<LogLevel> = std::sync::OnceLock::new();
|
pub(crate) static LOG_LEVEL: std::sync::OnceLock<LogLevel> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum LogLevel {
|
pub(crate) enum LogLevel {
|
||||||
Error = 1,
|
Error = 1,
|
||||||
Warn = 2,
|
Warn = 2,
|
||||||
Info = 3,
|
Info = 3,
|
||||||
|
|
@ -34,40 +34,44 @@ impl LogLevel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! log_error {
|
macro_rules! log_error {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info);
|
let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info);
|
||||||
if level >= LogLevel::Error {
|
if level >= $crate::LogLevel::Error {
|
||||||
let ts = $crate::format_timestamp();
|
let ts = $crate::format_timestamp();
|
||||||
eprintln!("[{}] [ERROR] {}", ts, format!($($arg)*));
|
eprintln!("[{}] [ERROR] {}", ts, format!($($arg)*));
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! log_warn {
|
macro_rules! log_warn {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info);
|
let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info);
|
||||||
if level >= LogLevel::Warn {
|
if level >= $crate::LogLevel::Warn {
|
||||||
let ts = $crate::format_timestamp();
|
let ts = $crate::format_timestamp();
|
||||||
eprintln!("[{}] [WARN] {}", ts, format!($($arg)*));
|
eprintln!("[{}] [WARN] {}", ts, format!($($arg)*));
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! log_info {
|
macro_rules! log_info {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info);
|
let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info);
|
||||||
if level >= LogLevel::Info {
|
if level >= $crate::LogLevel::Info {
|
||||||
let ts = $crate::format_timestamp();
|
let ts = $crate::format_timestamp();
|
||||||
eprintln!("[{}] [INFO] {}", ts, format!($($arg)*));
|
eprintln!("[{}] [INFO] {}", ts, format!($($arg)*));
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! log_debug {
|
macro_rules! log_debug {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info);
|
let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info);
|
||||||
if level >= LogLevel::Debug {
|
if level >= $crate::LogLevel::Debug {
|
||||||
let ts = $crate::format_timestamp();
|
let ts = $crate::format_timestamp();
|
||||||
eprintln!("[{}] [DEBUG] {}", ts, format!($($arg)*));
|
eprintln!("[{}] [DEBUG] {}", ts, format!($($arg)*));
|
||||||
}
|
}
|
||||||
|
|
@ -243,44 +247,6 @@ async fn main() {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use log::Log;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simple_logger_enabled() {
|
|
||||||
let logger = SimpleLogger {
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Info level should be enabled
|
|
||||||
let info_metadata = log::MetadataBuilder::new()
|
|
||||||
.level(log::Level::Info)
|
|
||||||
.target("test")
|
|
||||||
.build();
|
|
||||||
assert!(logger.enabled(&info_metadata));
|
|
||||||
|
|
||||||
// Debug level should not be enabled
|
|
||||||
let debug_metadata = log::MetadataBuilder::new()
|
|
||||||
.level(log::Level::Debug)
|
|
||||||
.target("test")
|
|
||||||
.build();
|
|
||||||
assert!(!logger.enabled(&debug_metadata));
|
|
||||||
|
|
||||||
// Error level should be enabled
|
|
||||||
let error_metadata = log::MetadataBuilder::new()
|
|
||||||
.level(log::Level::Error)
|
|
||||||
.target("test")
|
|
||||||
.build();
|
|
||||||
assert!(logger.enabled(&error_metadata));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simple_logger_flush() {
|
|
||||||
let logger = SimpleLogger {
|
|
||||||
level: LevelFilter::Info,
|
|
||||||
};
|
|
||||||
// flush() does nothing, just verify it's callable
|
|
||||||
logger.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_http_to_websocket() {
|
fn test_convert_http_to_websocket() {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use log::info;
|
|
||||||
|
|
||||||
// Get version at runtime - prefers BUILD_VERSION from build.rs, falls back to Cargo.toml
|
// Get version at runtime - prefers BUILD_VERSION from build.rs, falls back to Cargo.toml
|
||||||
fn current_version() -> &'static str {
|
fn current_version() -> &'static str {
|
||||||
option_env!("BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
|
option_env!("BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
|
||||||
|
|
@ -8,8 +6,8 @@ fn current_version() -> &'static str {
|
||||||
/// Startup check - logs current version
|
/// Startup check - logs current version
|
||||||
pub fn check_for_updates() {
|
pub fn check_for_updates() {
|
||||||
let current_ver = current_version();
|
let current_ver = current_version();
|
||||||
info!("Current version: {}", current_ver);
|
crate::log_info!("Current version: {}", current_ver);
|
||||||
info!("Watchtower will automatically update to new versions");
|
crate::log_info!("Watchtower will automatically update to new versions");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -59,17 +59,17 @@ impl AgentClient {
|
||||||
// Strip trailing slash from base URL to avoid double slashes
|
// Strip trailing slash from base URL to avoid double slashes
|
||||||
let base_url = url.trim_end_matches('/');
|
let base_url = url.trim_end_matches('/');
|
||||||
let ws_url = format!("{}/socket/agent/websocket", base_url);
|
let ws_url = format!("{}/socket/agent/websocket", base_url);
|
||||||
log::info!("Connecting to WebSocket: {}", ws_url);
|
crate::log_info!("Connecting to WebSocket: {}", ws_url);
|
||||||
|
|
||||||
let (mut ws_stream, _) = connect_async(&ws_url)
|
let (mut ws_stream, _) = connect_async(&ws_url)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("WebSocket connection failed: {}", e);
|
crate::log_error!("WebSocket connection failed: {}", e);
|
||||||
e
|
e
|
||||||
})
|
})
|
||||||
.map_err(|e| format!("Failed to connect to WebSocket: {}", e))?;
|
.map_err(|e| format!("Failed to connect to WebSocket: {}", e))?;
|
||||||
|
|
||||||
log::info!("Connected to Towerops server at {}", url);
|
crate::log_info!("Connected to Towerops server at {}", url);
|
||||||
|
|
||||||
let agent_id = generate_agent_id();
|
let agent_id = generate_agent_id();
|
||||||
let (result_tx, result_rx) = mpsc::unbounded_channel();
|
let (result_tx, result_rx) = mpsc::unbounded_channel();
|
||||||
|
|
@ -85,7 +85,7 @@ impl AgentClient {
|
||||||
|
|
||||||
let join_text = serde_json::to_string(&join_msg)?;
|
let join_text = serde_json::to_string(&join_msg)?;
|
||||||
ws_stream.send(WsMessage::Text(join_text)).await?;
|
ws_stream.send(WsMessage::Text(join_text)).await?;
|
||||||
log::info!(
|
crate::log_info!(
|
||||||
"Sent channel join request with token for agent:{}",
|
"Sent channel join request with token for agent:{}",
|
||||||
agent_id
|
agent_id
|
||||||
);
|
);
|
||||||
|
|
@ -122,15 +122,15 @@ impl AgentClient {
|
||||||
self.handle_text_message(&text).await?;
|
self.handle_text_message(&text).await?;
|
||||||
}
|
}
|
||||||
Some(Ok(WsMessage::Close(_))) => {
|
Some(Ok(WsMessage::Close(_))) => {
|
||||||
log::info!("Server closed connection");
|
crate::log_info!("Server closed connection");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
log::error!("WebSocket error: {}", e);
|
crate::log_error!("WebSocket error: {}", e);
|
||||||
return Err(e.into());
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::info!("Connection closed");
|
crate::log_info!("Connection closed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -163,7 +163,7 @@ impl AgentClient {
|
||||||
|
|
||||||
match phoenix_msg.event.as_str() {
|
match phoenix_msg.event.as_str() {
|
||||||
"phx_reply" => {
|
"phx_reply" => {
|
||||||
log::info!("Channel join reply: {:?}", phoenix_msg.payload);
|
crate::log_info!("Channel join reply: {:?}", phoenix_msg.payload);
|
||||||
}
|
}
|
||||||
"jobs" => {
|
"jobs" => {
|
||||||
// Extract binary protobuf from payload
|
// Extract binary protobuf from payload
|
||||||
|
|
@ -176,7 +176,7 @@ impl AgentClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::debug!("Ignoring unknown event: {}", phoenix_msg.event);
|
crate::log_debug!("Ignoring unknown event: {}", phoenix_msg.event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,11 +195,11 @@ impl AgentClient {
|
||||||
|
|
||||||
/// Process job list from server.
|
/// Process job list from server.
|
||||||
async fn handle_jobs(&self, job_list: AgentJobList) -> Result<()> {
|
async fn handle_jobs(&self, job_list: AgentJobList) -> Result<()> {
|
||||||
log::info!("Received {} jobs from server", job_list.jobs.len());
|
crate::log_info!("Received {} jobs from server", job_list.jobs.len());
|
||||||
|
|
||||||
for job in job_list.jobs {
|
for job in job_list.jobs {
|
||||||
let job_type = JobType::try_from(job.job_type).unwrap_or(JobType::Poll);
|
let job_type = JobType::try_from(job.job_type).unwrap_or(JobType::Poll);
|
||||||
log::info!("Executing job: {} (type: {:?})", job.job_id, job_type);
|
crate::log_info!("Executing job: {} (type: {:?})", job.job_id, job_type);
|
||||||
|
|
||||||
// Extract monitoring configuration before moving job
|
// Extract monitoring configuration before moving job
|
||||||
let monitoring_info = job.snmp_device.as_ref().and_then(|snmp_device| {
|
let monitoring_info = job.snmp_device.as_ref().and_then(|snmp_device| {
|
||||||
|
|
@ -224,13 +224,13 @@ impl AgentClient {
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = execute_job(job, result_tx).await {
|
if let Err(e) = execute_job(job, result_tx).await {
|
||||||
log::error!("Job execution failed: {}", e);
|
crate::log_error!("Job execution failed: {}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start monitoring task if enabled
|
// Start monitoring task if enabled
|
||||||
if let Some((ip, interval_seconds)) = monitoring_info {
|
if let Some((ip, interval_seconds)) = monitoring_info {
|
||||||
log::info!(
|
crate::log_info!(
|
||||||
"Starting ICMP monitoring for device {} every {} seconds",
|
"Starting ICMP monitoring for device {} every {} seconds",
|
||||||
device_id,
|
device_id,
|
||||||
interval_seconds
|
interval_seconds
|
||||||
|
|
@ -246,7 +246,7 @@ impl AgentClient {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
log::error!("Monitoring task failed: {}", e);
|
crate::log_error!("Monitoring task failed: {}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +277,7 @@ impl AgentClient {
|
||||||
let text = serde_json::to_string(&msg)?;
|
let text = serde_json::to_string(&msg)?;
|
||||||
self.ws_stream.send(WsMessage::Text(text)).await?;
|
self.ws_stream.send(WsMessage::Text(text)).await?;
|
||||||
|
|
||||||
log::debug!("Sent heartbeat");
|
crate::log_debug!("Sent heartbeat");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,7 +295,7 @@ impl AgentClient {
|
||||||
let text = serde_json::to_string(&msg)?;
|
let text = serde_json::to_string(&msg)?;
|
||||||
self.ws_stream.send(WsMessage::Text(text)).await?;
|
self.ws_stream.send(WsMessage::Text(text)).await?;
|
||||||
|
|
||||||
log::debug!("Sent SNMP result for device {}", result.device_id);
|
crate::log_debug!("Sent SNMP result for device {}", result.device_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ impl AgentClient {
|
||||||
let text = serde_json::to_string(&msg)?;
|
let text = serde_json::to_string(&msg)?;
|
||||||
self.ws_stream.send(WsMessage::Text(text)).await?;
|
self.ws_stream.send(WsMessage::Text(text)).await?;
|
||||||
|
|
||||||
log::debug!(
|
crate::log_debug!(
|
||||||
"Sent monitoring check for device {}: {}",
|
"Sent monitoring check for device {}: {}",
|
||||||
check.device_id,
|
check.device_id,
|
||||||
check.status
|
check.status
|
||||||
|
|
@ -351,7 +351,7 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender<SnmpResult>
|
||||||
oid_values.insert(oid.clone(), value_to_string(value));
|
oid_values.insert(oid.clone(), value_to_string(value));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("SNMP GET failed for OID {}: {}", oid, e);
|
crate::log_warn!("SNMP GET failed for OID {}: {}", oid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -375,7 +375,7 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender<SnmpResult>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("SNMP WALK failed for OID {}: {}", base_oid, e);
|
crate::log_warn!("SNMP WALK failed for OID {}: {}", base_oid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -393,7 +393,7 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender<SnmpResult>
|
||||||
.as_secs() as i64,
|
.as_secs() as i64,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!(
|
crate::log_info!(
|
||||||
"Collected {} OID values for job {}",
|
"Collected {} OID values for job {}",
|
||||||
result.oid_values.len(),
|
result.oid_values.len(),
|
||||||
job.job_id
|
job.job_id
|
||||||
|
|
@ -549,7 +549,7 @@ async fn run_monitoring_task(
|
||||||
let ip_addr: std::net::IpAddr = match ip.parse() {
|
let ip_addr: std::net::IpAddr = match ip.parse() {
|
||||||
Ok(addr) => addr,
|
Ok(addr) => addr,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Invalid IP address for device {}: {}", device_id, e);
|
crate::log_error!("Invalid IP address for device {}: {}", device_id, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -568,11 +568,11 @@ async fn run_monitoring_task(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = check_tx.send(check) {
|
if let Err(e) = check_tx.send(check) {
|
||||||
log::error!("Failed to send monitoring check: {}", e);
|
crate::log_error!("Failed to send monitoring check: {}", e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!(
|
crate::log_debug!(
|
||||||
"ICMP ping successful for {}: {:.2}ms",
|
"ICMP ping successful for {}: {:.2}ms",
|
||||||
ip,
|
ip,
|
||||||
rtt.as_secs_f64() * 1000.0
|
rtt.as_secs_f64() * 1000.0
|
||||||
|
|
@ -589,11 +589,11 @@ async fn run_monitoring_task(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(send_err) = check_tx.send(check) {
|
if let Err(send_err) = check_tx.send(check) {
|
||||||
log::error!("Failed to send monitoring check: {}", send_err);
|
crate::log_error!("Failed to send monitoring check: {}", send_err);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::warn!("ICMP ping failed for {}: {}", ip, e);
|
crate::log_warn!("ICMP ping failed for {}: {}", ip, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -663,8 +663,8 @@ mod tests {
|
||||||
let uptime = get_uptime_seconds();
|
let uptime = get_uptime_seconds();
|
||||||
// On Linux with /proc/uptime, should return non-zero
|
// On Linux with /proc/uptime, should return non-zero
|
||||||
// On other platforms or if file doesn't exist, returns 0
|
// On other platforms or if file doesn't exist, returns 0
|
||||||
// Just verify it's callable and returns a number
|
// uptime is u64, so always >= 0 - just verify it's callable
|
||||||
assert!(uptime >= 0);
|
let _ = uptime;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue