Skip to content

Commit ba76180

Browse files
authored
Fix tests and add CI for Rust crates (#54)
* Fix tests and add CI for Rust crates * Format crates * Work around an error of incomplete code * Disable yamcs integration tests in coverage
1 parent 584808f commit ba76180

43 files changed

Lines changed: 944 additions & 753 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: Build and Test Rust Crates
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'crates/**'
9+
- 'Cargo.toml'
10+
- 'Cargo.lock'
11+
- '.github/workflows/build-rust-crates.yml'
12+
pull_request:
13+
paths:
14+
- 'crates/**'
15+
- 'Cargo.toml'
16+
- 'Cargo.lock'
17+
- '.github/workflows/build-rust-crates.yml'
18+
workflow_dispatch:
19+
20+
jobs:
21+
test:
22+
name: Test Rust Crates
23+
runs-on: ${{ matrix.os }}
24+
strategy:
25+
matrix:
26+
os: [ubuntu-latest, macos-latest]
27+
rust: [stable]
28+
29+
steps:
30+
- name: Checkout code
31+
uses: actions/checkout@v4
32+
33+
- name: Install Rust toolchain
34+
uses: dtolnay/rust-toolchain@master
35+
with:
36+
toolchain: ${{ matrix.rust }}
37+
components: rustfmt, clippy
38+
39+
- name: Install Protocol Buffers Compiler
40+
uses: arduino/setup-protoc@v3
41+
with:
42+
version: "25.x"
43+
repo-token: ${{ secrets.GITHUB_TOKEN }}
44+
45+
- name: Cache cargo registry
46+
uses: actions/cache@v4
47+
with:
48+
path: ~/.cargo/registry
49+
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
50+
restore-keys: |
51+
${{ runner.os }}-cargo-registry-
52+
53+
- name: Cache cargo index
54+
uses: actions/cache@v4
55+
with:
56+
path: ~/.cargo/git
57+
key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }}
58+
restore-keys: |
59+
${{ runner.os }}-cargo-git-
60+
61+
- name: Cache cargo build
62+
uses: actions/cache@v4
63+
with:
64+
path: target
65+
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
66+
restore-keys: |
67+
${{ runner.os }}-cargo-build-target-
68+
69+
- name: Build all crates
70+
run: cargo build --workspace --all-features --verbose
71+
72+
- name: Check formatting
73+
run: cargo fmt --all -- --check
74+
75+
- name: Run clippy
76+
run: cargo clippy --all-targets --all-features --no-deps -- -W clippy::all
77+
continue-on-error: true
78+
79+
- name: Run unit tests
80+
run: cargo test --workspace --all-features --lib --verbose
81+
82+
- name: Run integration tests (excluding yamcs-http)
83+
run: cargo test --workspace --all-features --test '*' --exclude yamcs-http --verbose
84+
85+
- name: Run doc tests
86+
run: cargo test --workspace --all-features --doc --verbose
87+
88+
coverage:
89+
name: Code Coverage
90+
runs-on: ubuntu-latest
91+
92+
steps:
93+
- name: Checkout code
94+
uses: actions/checkout@v4
95+
96+
- name: Install Rust toolchain
97+
uses: dtolnay/rust-toolchain@stable
98+
99+
- name: Install Protocol Buffers Compiler
100+
uses: arduino/setup-protoc@v3
101+
with:
102+
version: "25.x"
103+
repo-token: ${{ secrets.GITHUB_TOKEN }}
104+
105+
- name: Install cargo-tarpaulin
106+
run: cargo install cargo-tarpaulin
107+
108+
- name: Generate coverage
109+
run: cargo tarpaulin --workspace --all-features --exclude yamcs-http --out xml --timeout 300
110+
111+
- name: Upload coverage to Codecov
112+
uses: codecov/codecov-action@v4
113+
with:
114+
files: ./cobertura.xml
115+
flags: rust
116+
fail_ci_if_error: false

crates/hermes-data/src/de/context.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ impl<'a> Context<'a> {
131131
pub(crate) fn get_bits(&mut self, num_bits: usize, order: ByteOrder) -> Result<u64> {
132132
assert!(num_bits <= 64, "Invalid bit count {}", num_bits);
133133

134-
if (self.position + num_bits) / 8 + 1 >= self.data.len() {
134+
let end_pos = self.position + num_bits;
135+
136+
if (end_pos % 8 == 0) && (end_pos / 8) > self.data.len() {
137+
Err(Error::Eos)
138+
} else if (end_pos % 8 > 0) && (end_pos / 8) >= self.data.len() {
135139
Err(Error::Eos)
136140
} else {
137141
let r = self.data.get(self.position, num_bits, order);

crates/hermes-data/src/de/expr.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::de::Context;
22
use crate::de::ParameterRefOrValue;
3+
use crate::error::InvalidComparison;
34
use crate::{Error, ParameterInstanceRef, Value};
45
use tracing::warn;
56

@@ -89,23 +90,23 @@ fn comparison(
8990
(Value::Enumerated(l), Value::Enumerated(r)) => Ok(builtin_comparison(op, l, r)),
9091

9192
// TODO(tumbar) We can probably implement more comparisons
92-
(_, _) => Err(Error::InvalidComparison(
93-
op.clone(),
94-
left.clone(),
95-
right.clone(),
96-
)), // (Value::Array(l), Value::Array(r)) => {
97-
// if l.len() != r.len() || *op != hermes_xtce::ComparisonOperatorsType::Eq {
98-
// Err(Error::InvalidComparison(
99-
// op.clone(),
100-
// left.clone(),
101-
// right.clone(),
102-
// ))
103-
// } else {
104-
// Ok(l.iter()
105-
// .zip(r)
106-
// .all(|(l, r)| comparison(&hermes_xtce::ComparisonOperatorsType::Eq, left, right)))
107-
// }
108-
// }
93+
(_, _) => Err(Error::InvalidComparison(Box::new(InvalidComparison {
94+
op: op.clone(),
95+
left: left.clone(),
96+
right: right.clone(),
97+
}))), // (Value::Array(l), Value::Array(r)) => {
98+
// if l.len() != r.len() || *op != hermes_xtce::ComparisonOperatorsType::Eq {
99+
// Err(Error::InvalidComparison(
100+
// op.clone(),
101+
// left.clone(),
102+
// right.clone(),
103+
// ))
104+
// } else {
105+
// Ok(l.iter()
106+
// .zip(r)
107+
// .all(|(l, r)| comparison(&hermes_xtce::ComparisonOperatorsType::Eq, left, right)))
108+
// }
109+
// }
109110
}
110111
}
111112

crates/hermes-data/src/error.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
1+
use std::fmt::Display;
12
use thiserror::Error;
23

34
use crate::Value;
45

56
/// Result type alias for hermes-data operations
67
pub type Result<T> = std::result::Result<T, Error>;
78

9+
#[derive(Debug)]
10+
pub struct InvalidComparison {
11+
pub op: hermes_xtce::ComparisonOperatorsType,
12+
pub left: Value,
13+
pub right: Value,
14+
}
15+
16+
impl Display for InvalidComparison {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
f.write_fmt(format_args!("{} {} {}", self.left, self.op, self.right))
19+
}
20+
}
21+
822
/// Error types for hermes-data operations
923
#[derive(Error, Debug)]
1024
pub enum Error {
@@ -44,8 +58,8 @@ pub enum Error {
4458
#[error("Operation Not Implemented: {0}")]
4559
NotImplemented(&'static str),
4660

47-
#[error("Invalid comparison '{0}' between values: {1}, {2}")]
48-
InvalidComparison(hermes_xtce::ComparisonOperatorsType, Value, Value),
61+
#[error("Invalid comparison: {0}")]
62+
InvalidComparison(Box<InvalidComparison>),
4963

5064
#[error("Mismatching checksum, computed {0}, received {1}")]
5165
ChecksumMismatch(u16, u16),

crates/hermes-data/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ mod command;
33
pub mod de;
44
mod error;
55
mod framing;
6+
mod item;
67
mod types;
78
mod util;
89
mod xtce;
9-
mod item;
1010

1111
pub use calibrator::*;
1212
pub use error::{Error, Result};
1313
pub use framing::*;
14-
pub use types::*;
1514
pub use item::*;
15+
pub use types::*;
1616

1717
use std::collections::HashMap;
1818
use std::sync::Arc;

crates/hermes-data/src/xtce/collection.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/// Functions for traversing the XTCE SpaceSystem tree and collecting items
22
use std::collections::HashMap;
33

4-
54
use super::utils::make_qualified_name;
65

76
/// Intermediate structure holding a container during Pass 1 before dependencies are resolved

crates/hermes-data/src/xtce/construction.rs

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@
22
use std::collections::HashMap;
33
use std::sync::Arc;
44

5-
65
use super::collection::{UnresolvedContainer, UnresolvedParameter, UnresolvedParameterType};
76
use super::conversion::convert_restriction_criteria;
87
use super::resolution::{resolve_container_reference, resolve_parameter_type_name};
98
use crate::de::{RestrictionCriteria, SequenceContainerType};
10-
use crate::{Item, Parameter};
119
use crate::xtce::container::convert_entry;
1210
use crate::xtce::types::{
1311
convert_parameter_type_set, convert_parameter_type_set_with_context,
1412
convert_parameter_type_set_with_parameters,
1513
};
1614
use crate::{Error, Result};
15+
use crate::{Item, Parameter};
1716

1817
/// Construct parameter types in multiple passes:
1918
/// Pass 1: Simple types (int, float, string, bool, enum, time) - no dependencies
@@ -70,35 +69,68 @@ pub(crate) fn construct_parameter_types_pass1(
7069
}
7170

7271
/// Pass 2: Construct Aggregate types (after simple types are available)
72+
/// This uses multi-pass construction to handle dependencies between aggregate types.
7373
pub(crate) fn construct_parameter_types_pass2_aggregates(
7474
deferred_aggregate_types: Vec<(String, UnresolvedParameterType)>,
7575
completed: &mut HashMap<String, Arc<crate::Type>>,
7676
) -> Result<()> {
77-
for (qualified_name, unresolved_type) in deferred_aggregate_types {
78-
match convert_parameter_type_set_with_context(
79-
&unresolved_type.xml,
80-
&unresolved_type.space_system_path,
81-
&completed,
82-
) {
83-
Ok(type_) => {
84-
completed.insert(qualified_name, Arc::new(type_));
85-
}
86-
Err(Error::NotImplemented(msg)) => {
87-
tracing::warn!(
88-
"Skipping unsupported parameter type '{}': {}",
89-
qualified_name,
90-
msg
91-
);
77+
let mut remaining = deferred_aggregate_types;
78+
let mut failed_permanently = Vec::new();
79+
80+
// Keep trying until we make no more progress
81+
while !remaining.is_empty() {
82+
let mut still_deferred = Vec::new();
83+
let mut made_progress = false;
84+
85+
for (qualified_name, unresolved_type) in remaining {
86+
match convert_parameter_type_set_with_context(
87+
&unresolved_type.xml,
88+
&unresolved_type.space_system_path,
89+
&completed,
90+
) {
91+
Ok(type_) => {
92+
completed.insert(qualified_name, Arc::new(type_));
93+
made_progress = true;
94+
}
95+
Err(Error::NotImplemented(msg)) => {
96+
// These are permanently unsupported types
97+
tracing::warn!(
98+
"Skipping unsupported parameter type '{}': {}",
99+
qualified_name,
100+
msg
101+
);
102+
failed_permanently.push(qualified_name);
103+
}
104+
Err(Error::InvalidXtce(ref msg)) if msg.contains("not found") => {
105+
// This is likely a missing dependency - defer for next pass
106+
still_deferred.push((qualified_name, unresolved_type));
107+
}
108+
Err(e) => {
109+
// Other errors should be logged but not retried
110+
tracing::warn!(
111+
"Failed to construct aggregate type '{}': {}",
112+
qualified_name,
113+
e
114+
);
115+
failed_permanently.push(qualified_name);
116+
}
92117
}
93-
Err(e) => {
118+
}
119+
120+
// If we didn't make progress and still have deferred types, they must have unresolvable dependencies
121+
if !made_progress && !still_deferred.is_empty() {
122+
for (qualified_name, _) in still_deferred {
94123
tracing::warn!(
95-
"Failed to construct aggregate type '{}': {}",
96-
qualified_name,
97-
e
124+
"Failed to construct aggregate type '{}': unresolvable type dependencies",
125+
qualified_name
98126
);
99127
}
128+
break;
100129
}
130+
131+
remaining = still_deferred;
101132
}
133+
102134
Ok(())
103135
}
104136

crates/hermes-data/src/xtce/container.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use crate::Parameter;
12
use crate::de::{
23
ContainerRef, EntryKind, EntryType, LocationInContainerInBits, ReferenceLocation, Repeat,
34
};
4-
use crate::Parameter;
55

66
use crate::{Error, IntegerValue, ParameterInstanceRef, ParameterRef};
77

crates/hermes-data/src/xtce/conversion.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
use std::collections::HashMap;
33
use std::sync::Arc;
44

5-
5+
use crate::Parameter;
66
use crate::de::{
77
BooleanExpression, Comparison, ComparisonCheck, ParameterRefOrValue, RestrictionCriteria,
88
};
9-
use crate::Parameter;
109

1110
use crate::{Error, ParameterInstanceRef, ParameterRef, Result, Value};
1211

crates/hermes-data/src/xtce/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod utils;
1717

1818
// Re-export public types and functions
1919
pub(crate) use collection::{
20-
collect_containers, collect_parameter_types, collect_parameters, UnresolvedContainer,
20+
UnresolvedContainer, collect_containers, collect_parameter_types, collect_parameters,
2121
};
2222
pub(crate) use construction::{
2323
build_dependency_graph, construct_containers, construct_parameter_types_pass1,

0 commit comments

Comments
 (0)