mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
CD/CI git build workflow and unit testing + dependabot intergration for security updates
This commit is contained in:
parent
bd2111d77a
commit
a6523bf105
6 changed files with 182 additions and 15 deletions
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal 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
70
.github/workflows/build.yml
vendored
Normal 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
17
.gitignore
vendored
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,17 @@ impl LtcState {
|
||||||
match frame.status.as_str() {
|
match frame.status.as_str() {
|
||||||
"LOCK" => {
|
"LOCK" => {
|
||||||
self.lock_count += 1;
|
self.lock_count += 1;
|
||||||
|
|
||||||
|
// Every 5 seconds, recompute whether HH:MM:SS matches local time
|
||||||
|
let now_secs = Utc::now().timestamp();
|
||||||
|
if now_secs - self.last_match_check >= 5 {
|
||||||
|
self.last_match_status = if frame.matches_system_time() {
|
||||||
|
"IN SYNC".into()
|
||||||
|
} else {
|
||||||
|
"OUT OF SYNC".into()
|
||||||
|
};
|
||||||
|
self.last_match_check = now_secs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"FREE" => {
|
"FREE" => {
|
||||||
self.free_count += 1;
|
self.free_count += 1;
|
||||||
|
|
@ -86,17 +97,6 @@ impl LtcState {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every 5 seconds, recompute whether HH:MM:SS matches local time
|
|
||||||
let now_secs = Utc::now().timestamp();
|
|
||||||
if now_secs - self.last_match_check >= 5 {
|
|
||||||
self.last_match_status = if frame.matches_system_time() {
|
|
||||||
"IN SYNC".into()
|
|
||||||
} else {
|
|
||||||
"OUT OF SYNC".into()
|
|
||||||
};
|
|
||||||
self.last_match_check = now_secs;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue