CD/CI git build workflow and unit testing + dependabot intergration for security updates

This commit is contained in:
Chaos Rogers 2025-07-18 12:50:53 +01:00
parent bd2111d77a
commit a6523bf105
6 changed files with 182 additions and 15 deletions

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/" # Location of Cargo.toml
schedule:
interval: "weekly"

70
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,70 @@
name: Build for Raspberry Pi
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
CARGO_TERM_COLOR: always
# Target for 64-bit Raspberry Pi (Raspberry Pi OS)
RUST_TARGET: aarch64-unknown-linux-gnu
jobs:
build:
name: Build for aarch64
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ env.RUST_TARGET }}
- name: Install cross-compilation dependencies
run: |
sudo dpkg --add-architecture arm64
# Configure sources for ARM64 packages - all ARM64 packages come from ports.ubuntu.com
sudo tee /etc/apt/sources.list.d/arm64.list > /dev/null <<'EOF'
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse
EOF
# Modify existing sources to exclude arm64 architecture
sudo sed -i 's/^deb /deb [arch=amd64] /' /etc/apt/sources.list
sudo apt-get update -y
# Install build tools and cross-compilation libraries for Raspberry Pi 5
sudo apt-get install -y gcc-aarch64-linux-gnu libudev-dev:arm64 pkg-config cmake libudev-dev
# Ensure pkg-config can find ARM64 libraries
sudo apt-get install -y libpkgconf3:arm64
- name: Install Rust dependencies
run: cargo fetch --target ${{ env.RUST_TARGET }}
- name: Build release binary
run: cargo build --release --target ${{ env.RUST_TARGET }}
env:
# Set linker for the target
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
# Configure pkg-config for cross-compilation
PKG_CONFIG_ALLOW_CROSS: 1
PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig
PKG_CONFIG_LIBDIR: /usr/lib/aarch64-linux-gnu/pkgconfig
PKG_CONFIG_SYSROOT_DIR: /
PKG_CONFIG_ALLOW_SYSTEM_LIBS: 1
PKG_CONFIG_ALLOW_SYSTEM_CFLAGS: 1
# Add library path for the cross-compiler's linker
RUSTFLAGS: -L/usr/lib/aarch64-linux-gnu
- name: Run tests on native platform
run: cargo test --release --bin ntp_timeturner
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: timeturner-aarch64
path: target/${{ env.RUST_TARGET }}/release/ntp_timeturner

17
.gitignore vendored
View file

@ -361,3 +361,20 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
.aider*
# Generated by Cargo
# will have compiled files and executables
debug
target
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

View file

@ -6,8 +6,8 @@ edition = "2021"
[dependencies] [dependencies]
serialport = "4.2" serialport = "4.2"
chrono = "0.4" chrono = "0.4"
crossterm = "0.27" crossterm = "0.29"
regex = "1.11" regex = "1.11"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
notify = "5.1.0" notify = "8.1.0"

View file

@ -28,8 +28,13 @@ Inspired by the TimeTurner in the Harry Potter series, this project synchronises
--- ---
## 🚀 Installation ## 🚀 Installation (to update)
For Rust install you can do
```bash
cargo install --git https://github.com/cjfranko/NTP-Timeturner
```
Clone and run the installer: Clone and run the installer:
```bash ```bash

View file

@ -77,14 +77,6 @@ impl LtcState {
match frame.status.as_str() { match frame.status.as_str() {
"LOCK" => { "LOCK" => {
self.lock_count += 1; self.lock_count += 1;
}
"FREE" => {
self.free_count += 1;
self.clear_offsets();
self.last_match_status = "UNKNOWN".into();
}
_ => {}
}
// Every 5 seconds, recompute whether HH:MM:SS matches local time // Every 5 seconds, recompute whether HH:MM:SS matches local time
let now_secs = Utc::now().timestamp(); let now_secs = Utc::now().timestamp();
@ -96,6 +88,14 @@ impl LtcState {
}; };
self.last_match_check = now_secs; self.last_match_check = now_secs;
} }
}
"FREE" => {
self.free_count += 1;
self.clear_offsets();
self.last_match_status = "UNKNOWN".into();
}
_ => {}
}
self.latest = Some(frame); self.latest = Some(frame);
} }
@ -135,3 +135,72 @@ impl LtcState {
&self.last_match_status &self.last_match_status
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Local, Utc};
fn get_test_frame(status: &str, h: u32, m: u32, s: u32) -> LtcFrame {
LtcFrame {
status: status.to_string(),
hours: h,
minutes: m,
seconds: s,
frames: 0,
frame_rate: 25.0,
timestamp: Utc::now(),
}
}
#[test]
fn test_ltc_frame_matches_system_time() {
let now = Local::now();
let frame = get_test_frame("LOCK", now.hour(), now.minute(), now.second());
assert!(frame.matches_system_time());
}
#[test]
fn test_ltc_frame_does_not_match_system_time() {
let now = Local::now();
// Create a time that is one hour ahead, wrapping around 23:00
let different_hour = (now.hour() + 1) % 24;
let frame = get_test_frame("LOCK", different_hour, now.minute(), now.second());
assert!(!frame.matches_system_time());
}
#[test]
fn test_ltc_state_update_lock() {
let mut state = LtcState::new();
let frame = get_test_frame("LOCK", 10, 20, 30);
state.update(frame);
assert_eq!(state.lock_count, 1);
assert_eq!(state.free_count, 0);
assert!(state.latest.is_some());
}
#[test]
fn test_ltc_state_update_free() {
let mut state = LtcState::new();
state.record_offset(100);
assert!(!state.offset_history.is_empty());
let frame = get_test_frame("FREE", 10, 20, 30);
state.update(frame);
assert_eq!(state.lock_count, 0);
assert_eq!(state.free_count, 1);
assert!(state.offset_history.is_empty()); // Offsets should be cleared
assert_eq!(state.last_match_status, "UNKNOWN");
}
#[test]
fn test_offset_history_management() {
let mut state = LtcState::new();
for i in 0..25 {
state.record_offset(i);
}
assert_eq!(state.offset_history.len(), 20);
assert_eq!(*state.offset_history.front().unwrap(), 5); // 0-4 are pushed out
assert_eq!(*state.offset_history.back().unwrap(), 24);
}
}