Skip to content

Commit 04fabae

Browse files
committed
test(aggregate): cover all of the AstarteObject functions
Signed-off-by: Joshua Chapman <joshua.chapman@secomind.com>
1 parent bea08fd commit 04fabae

File tree

3 files changed

+243
-6
lines changed

3 files changed

+243
-6
lines changed

Cargo.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@
22
#
33
# Copyright 2022 - 2025 SECO Mind Srl
44
#
5-
# SPDX-License-Identifier: CC0-1.0
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
# SPDX-License-Identifier: Apache-2.0
618

719
[package]
820
name = "astarte-device-sdk"

scripts/coverage.sh

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env bash
2+
3+
# This file is part of Astarte.
4+
#
5+
# Copyright 2025 SECO Mind Srl
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
#
19+
# SPDX-License-Identifier: Apache-2.0
20+
21+
set -exEuo pipefail
22+
23+
# Output directories for the profile files and coverage
24+
#
25+
# You'll find the coverage report and `lcov` file under: $CARGO_TARGET_DIR/debug/coverage/
26+
#
27+
# Use absolute paths every where
28+
CARGO_TARGET_DIR=$(
29+
cargo metadata --format-version 1 --no-deps --locked |
30+
jq '.target_directory' --raw-output
31+
)
32+
export CARGO_TARGET_DIR
33+
SRC_DIR="$(
34+
cargo +nightly locate-project |
35+
jq .root --raw-output |
36+
xargs dirname
37+
)"
38+
export SRC_DIR
39+
export PROFS_DIR="$CARGO_TARGET_DIR/profs"
40+
export LLVM_PROFILE_FILE="$PROFS_DIR/coverage-%p-%m.profraw"
41+
export COVERAGE_OUT_DIR="$CARGO_TARGET_DIR/debug/coverage"
42+
43+
# This require a nightly compiler
44+
#
45+
# Rustc options:
46+
#
47+
# - `instrument-coverage`: enable coverage for all
48+
# - `coverage-options=branch``: enable block and branch coverage (unstable option)
49+
#
50+
# See: https://doc.rust-lang.org/rustc/instrument-coverage.html
51+
export RUSTFLAGS="-Cinstrument-coverage -Zcoverage-options=branch"
52+
export CARGO_INCREMENTAL=0
53+
54+
# Helpful for testing changes in the generation options
55+
if [[ ${1:-} != '--no-gen' ]]; then
56+
cargo +nightly clean
57+
58+
mkdir -p "$COVERAGE_OUT_DIR"
59+
mkdir -p "$PROFS_DIR"
60+
61+
cargo +nightly test --locked --all-features --tests --no-fail-fast -p astarte-device-sdk
62+
fi
63+
64+
find_target_tool() {
65+
local libdir
66+
local tool_path
67+
68+
libdir=$(rustup run nightly rustc --print target-libdir)
69+
tool_path=$(realpath "$libdir/../bin/$1")
70+
71+
echo "$tool_path"
72+
}
73+
74+
rustup_llvm_profdata=$(find_target_tool llvm-profdata)
75+
rustup_llvm_cov=$(find_target_tool llvm-cov)
76+
77+
LLVM_PROFDATA=${LLVM_PROFDATA:-$rustup_llvm_profdata}
78+
LLVM_COV=${LLVM_COV:-$rustup_llvm_cov}
79+
80+
$LLVM_PROFDATA merge -sparse "$PROFS_DIR/"*.profraw -o "$PROFS_DIR/coverage.profdata"
81+
82+
object_files() {
83+
tests=$(
84+
cargo +nightly test --tests --all-features --no-run --message-format=json "$@" |
85+
jq -r "select(.profile.test == true) | .filenames[]" |
86+
grep -v dSYM -
87+
)
88+
89+
for file in $tests; do
90+
printf "%s %s " -object "$file"
91+
done
92+
}
93+
94+
export_lcov() {
95+
local src
96+
if [[ $1 == 'astarte-device-sdk' ]]; then
97+
src="$SRC_DIR/src"
98+
else
99+
src="$SRC_DIR/$1/src"
100+
fi
101+
102+
# shellcheck disable=2086,2046
103+
$LLVM_COV export \
104+
-format=lcov \
105+
-ignore-filename-regex='/.cargo/registry' \
106+
-instr-profile="$PROFS_DIR/coverage.profdata" \
107+
-ignore-filename-regex='.*test\.rs' \
108+
-ignore-filename-regex='.*mock\.rs' \
109+
-sources "$src" $(object_files -p "$1") \
110+
>"$COVERAGE_OUT_DIR/$1/lcov.info"
111+
}
112+
113+
filter_lcov() {
114+
local src
115+
if [[ $1 == 'astarte-device-sdk' ]]; then
116+
src="$SRC_DIR"
117+
else
118+
src="$SRC_DIR/$1"
119+
fi
120+
121+
grcov \
122+
"$COVERAGE_OUT_DIR/$1/lcov.info" \
123+
--binary-path "$CARGO_TARGET_DIR/debug" \
124+
--output-path "$COVERAGE_OUT_DIR/$1/" \
125+
--source-dir "$src" \
126+
--branch \
127+
--llvm \
128+
--excl-start 'mod test(s)?' \
129+
--output-type lcov,html
130+
}
131+
132+
list=(
133+
'astarte-device-sdk'
134+
)
135+
136+
for p in "${list[@]}"; do
137+
mkdir -p "$COVERAGE_OUT_DIR/$p/"
138+
139+
export_lcov "$p"
140+
141+
filter_lcov "$p"
142+
143+
cp -v "$COVERAGE_OUT_DIR/$p/lcov" "$COVERAGE_OUT_DIR/coverage-$p.info"
144+
145+
if [[ -n "${EXPORT_FOR_CI:-}" ]]; then
146+
cp -v "$COVERAGE_OUT_DIR/$p/lcov" "$PWD/coverage-$p.info"
147+
fi
148+
done

src/aggregate.rs

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl AstarteObject {
9393
Some(value)
9494
}
9595

96-
/// Returns the number of mapping elements.
96+
/// Returns the number of mapping data in the object.
9797
pub fn len(&self) -> usize {
9898
self.inner.len()
9999
}
@@ -122,16 +122,17 @@ impl FromIterator<(String, AstarteType)> for AstarteObject {
122122
}
123123
}
124124

125+
/// Serialize the [`AstarteObject`] as a map.
125126
impl Serialize for AstarteObject {
126127
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127128
where
128129
S: serde::Serializer,
129130
{
130-
let mut s_map = serializer.serialize_map(Some(self.len()))?;
131-
for (key, value) in self.iter() {
132-
s_map.serialize_entry(key, value)?;
131+
let mut s = serializer.serialize_map(Some(self.len()))?;
132+
for (name, value) in self.iter() {
133+
s.serialize_entry(name, value)?;
133134
}
134-
s_map.end()
135+
s.end()
135136
}
136137
}
137138

@@ -212,6 +213,8 @@ impl Value {
212213

213214
#[cfg(test)]
214215
mod tests {
216+
use pretty_assertions::assert_eq;
217+
215218
use super::*;
216219

217220
#[test]
@@ -221,12 +224,86 @@ mod tests {
221224
assert!(val.is_individual());
222225
assert_eq!(val.as_individual(), Some(&individual));
223226
assert_eq!(val.as_object(), None);
227+
assert_eq!(val.take_individual(), Some(individual));
228+
229+
let val = Value::Individual(AstarteType::Integer(42));
230+
assert_eq!(val.take_object(), None);
224231

225232
let val = Value::Object(AstarteObject::new());
226233
assert!(val.is_object());
227234
assert_eq!(val.as_individual(), None);
228235
assert_eq!(val.as_object(), Some(&AstarteObject::new()));
236+
assert_eq!(val.take_object(), Some(AstarteObject::new()));
237+
238+
let val = Value::Object(AstarteObject::new());
239+
assert_eq!(val.take_individual(), None);
229240

230241
assert!(Value::Unset.is_unset());
231242
}
243+
244+
#[test]
245+
fn create_with_capacity() {
246+
let exp = 10;
247+
let object = AstarteObject::with_capacity(exp);
248+
assert_eq!(object.inner.capacity(), exp);
249+
}
250+
251+
#[test]
252+
fn add_value_to_obj_and_replace() {
253+
let mut object = AstarteObject::new();
254+
let exp = AstarteType::from("foo");
255+
object.insert("foo".to_string(), exp.clone());
256+
assert_eq!(object.get("foo"), Some(&exp));
257+
258+
let exp = AstarteType::from("other");
259+
object.insert("foo".to_string(), exp.clone());
260+
assert_eq!(object.get("foo"), Some(&exp));
261+
}
262+
263+
#[test]
264+
fn iter_object_values() {
265+
let values = [
266+
("foo", AstarteType::from("foo")),
267+
("bar", AstarteType::from("bar")),
268+
("some", AstarteType::from("some")),
269+
]
270+
.map(|(n, v)| (n.to_string(), v));
271+
272+
let object = AstarteObject::from_iter(values.clone());
273+
274+
assert!(!object.is_empty());
275+
assert_eq!(object.len(), values.len());
276+
277+
for (exp, val) in object.iter().zip(&values) {
278+
assert_eq!(exp, val)
279+
}
280+
281+
for (exp, val) in object.into_key_values().zip(values) {
282+
assert_eq!(exp, val)
283+
}
284+
}
285+
286+
#[test]
287+
fn astarte_object_custom_serialize_map() {
288+
let values = [
289+
("foo", AstarteType::from("foo")),
290+
("bar", AstarteType::from("bar")),
291+
("some", AstarteType::from("some")),
292+
]
293+
.map(|(n, v)| (n.to_string(), v));
294+
295+
let object = AstarteObject::from_iter(values.clone());
296+
297+
let json = serde_json::to_string(&object).unwrap();
298+
299+
let de: serde_json::Value = serde_json::from_str(&json).unwrap();
300+
let map = de.as_object().unwrap();
301+
assert_eq!(map.len(), object.len());
302+
let foo = map.get("foo").and_then(serde_json::Value::as_str).unwrap();
303+
assert_eq!(foo, "foo");
304+
let bar = map.get("bar").and_then(serde_json::Value::as_str).unwrap();
305+
assert_eq!(bar, "bar");
306+
let some = map.get("some").and_then(serde_json::Value::as_str).unwrap();
307+
assert_eq!(some, "some");
308+
}
232309
}

0 commit comments

Comments
 (0)