Skip to content

Commit 7cbf9fe

Browse files
authored
test: improve types coverage (#369)
* ci: add Codecov coverage reporting * test: improve types coverage * docs: add Codecov badge * test: expand types coverage * ci: install grcov for coverage * ci: respect cargo target dir for grcov
1 parent 449bbc0 commit 7cbf9fe

33 files changed

Lines changed: 3568 additions & 35 deletions

.github/workflows/ci.yml

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,28 @@ jobs:
4848
- name: Run sqllogictest suite
4949
run: make test-slt
5050
# 4
51+
coverage:
52+
name: Rust coverage
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v4
56+
- uses: dtolnay/rust-toolchain@stable
57+
with:
58+
components: llvm-tools-preview
59+
- uses: Swatinem/rust-cache@v2
60+
- uses: taiki-e/install-action@grcov
61+
62+
- name: Generate Codecov report
63+
run: make codecov
64+
65+
- name: Upload coverage to Codecov
66+
uses: codecov/codecov-action@v5
67+
with:
68+
files: lcov.info
69+
flags: rust
70+
fail_ci_if_error: true
71+
token: ${{ secrets.CODECOV_TOKEN }}
72+
# 5
5173
wasm-tests:
5274
name: Wasm cargo tests
5375
runs-on: ubuntu-latest
@@ -65,7 +87,7 @@ jobs:
6587

6688
- name: Run wasm-bindgen tests (wasm32 target)
6789
run: make test-wasm
68-
# 5
90+
# 6
6991
wasm-examples:
7092
name: Wasm examples (nodejs)
7193
runs-on: ubuntu-latest
@@ -86,7 +108,7 @@ jobs:
86108

87109
- name: Run wasm example scripts
88110
run: make wasm-examples
89-
# 6
111+
# 7
90112
native-examples:
91113
name: Native examples
92114
runs-on: ubuntu-latest
@@ -97,7 +119,7 @@ jobs:
97119

98120
- name: Run native examples
99121
run: make native-examples
100-
# 7
122+
# 8
101123
python-tests:
102124
name: Python bindings tests
103125
runs-on: ubuntu-latest

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ kitesql_bench
2323
sqlite_bench
2424
kite_sql_tpcc
2525
copy.csv
26+
copy_query.csv
27+
lcov.info
28+
*.profraw
2629

2730
tests/data/row_20000.csv
2831
tests/data/distinct_rows.csv

Makefile

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Helper variables (override on invocation if needed).
22
CARGO ?= cargo
3+
GRCOV ?= grcov
34
WASM_PACK ?= wasm-pack
45
SQLLOGIC_PATH ?= tests/slt/**/*.slt
56
PYO3_PYTHON ?= /usr/bin/python3.12
@@ -9,8 +10,13 @@ TPCC_PPROF_OUTPUT ?= /tmp/tpcc_lmdb.svg
910
TPCC_HEAPTRACK_MEASURE_TIME ?= 300
1011
TPCC_HEAPTRACK_OUTPUT ?= /tmp/tpcc_lmdb_heaptrack
1112
TPCC_SQLITE_PROFILE ?= balanced
13+
CODECOV_OUTPUT ?= lcov.info
14+
COVERAGE_PROFILE_DIR ?= target/grcov/profraw
15+
COVERAGE_HTML_DIR ?= target/grcov/html
16+
COVERAGE_RUSTFLAGS ?= -Cinstrument-coverage
17+
COVERAGE_REPORT_ARGS ?= --llvm --ignore-not-existing --keep-only 'src/**' --ignore 'src/**/tests/**' --ignore 'tests/**' --ignore 'tpcc/**' --excl-start 'GRCOV_EXCL_START' --excl-stop 'GRCOV_EXCL_STOP'
1218

13-
.PHONY: test test-python test-wasm test-slt test-all wasm-build check tpcc tpcc-kitesql-rocksdb tpcc-kitesql-lmdb tpcc-lmdb-flamegraph tpcc-lmdb-heaptrack tpcc-sqlite tpcc-sqlite-practical tpcc-sqlite-balanced tpcc-dual cargo-check build wasm-examples native-examples fmt clippy
19+
.PHONY: test test-python test-wasm test-slt test-all codecov codecov-html wasm-build check tpcc tpcc-kitesql-rocksdb tpcc-kitesql-lmdb tpcc-lmdb-flamegraph tpcc-lmdb-heaptrack tpcc-sqlite tpcc-sqlite-practical tpcc-sqlite-balanced tpcc-dual cargo-check build wasm-examples native-examples fmt clippy
1420

1521
## Run default Rust tests in the current environment (non-WASM).
1622
test:
@@ -43,6 +49,27 @@ test-slt:
4349
## Convenience target to run every suite in sequence.
4450
test-all: test test-wasm test-slt test-python
4551

52+
## Generate an lcov coverage report for Codecov upload.
53+
codecov:
54+
bash -c "set -euo pipefail; \
55+
command -v $(GRCOV) >/dev/null || { echo 'grcov is not installed; run: cargo install grcov'; exit 1; }; \
56+
rm -rf '$(COVERAGE_PROFILE_DIR)' '$(CODECOV_OUTPUT)'; \
57+
mkdir -p '$(COVERAGE_PROFILE_DIR)'; \
58+
CARGO_INCREMENTAL=0 RUSTFLAGS=\"$${RUSTFLAGS:-} $(COVERAGE_RUSTFLAGS)\" LLVM_PROFILE_FILE='$(COVERAGE_PROFILE_DIR)/kitesql-%p-%m.profraw' $(CARGO) test --all --features decimal; \
59+
CARGO_INCREMENTAL=0 RUSTFLAGS=\"$${RUSTFLAGS:-} $(COVERAGE_RUSTFLAGS)\" LLVM_PROFILE_FILE='$(COVERAGE_PROFILE_DIR)/kitesql-%p-%m.profraw' $(CARGO) run -p sqllogictest-test -- --path '$(SQLLOGIC_PATH)'; \
60+
$(GRCOV) . --binary-path \"$${CARGO_TARGET_DIR:-target}/debug\" -s . -t lcov $(COVERAGE_REPORT_ARGS) -o '$(CODECOV_OUTPUT)'"
61+
62+
## Generate a local HTML coverage report.
63+
codecov-html:
64+
bash -c "set -euo pipefail; \
65+
command -v $(GRCOV) >/dev/null || { echo 'grcov is not installed; run: cargo install grcov'; exit 1; }; \
66+
rm -rf '$(COVERAGE_PROFILE_DIR)' '$(COVERAGE_HTML_DIR)'; \
67+
mkdir -p '$(COVERAGE_PROFILE_DIR)' '$(COVERAGE_HTML_DIR)'; \
68+
CARGO_INCREMENTAL=0 RUSTFLAGS=\"$${RUSTFLAGS:-} $(COVERAGE_RUSTFLAGS)\" LLVM_PROFILE_FILE='$(COVERAGE_PROFILE_DIR)/kitesql-%p-%m.profraw' $(CARGO) test --all --features decimal; \
69+
CARGO_INCREMENTAL=0 RUSTFLAGS=\"$${RUSTFLAGS:-} $(COVERAGE_RUSTFLAGS)\" LLVM_PROFILE_FILE='$(COVERAGE_PROFILE_DIR)/kitesql-%p-%m.profraw' $(CARGO) run -p sqllogictest-test -- --path '$(SQLLOGIC_PATH)'; \
70+
$(GRCOV) . --binary-path \"$${CARGO_TARGET_DIR:-target}/debug\" -s . -t html $(COVERAGE_REPORT_ARGS) -o '$(COVERAGE_HTML_DIR)'"
71+
@echo "Coverage report: $(COVERAGE_HTML_DIR)/index.html"
72+
4673
## Run formatting (check mode) across the workspace.
4774
fmt:
4875
$(CARGO) fmt --all -- --check

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
</p>
1919
<p align="center">
2020
<a href="https://github.com/KipData/KiteSQL/actions/workflows/ci.yml"><img src="https://github.com/KipData/KiteSQL/actions/workflows/ci.yml/badge.svg" alt="CI"></img></a>
21+
<a href="https://codecov.io/github/KipData/KiteSQL"><img src="https://codecov.io/github/KipData/KiteSQL/branch/main/graph/badge.svg" alt="Codecov"></img></a>
2122
<a href="https://deepwiki.com/KipData/KiteSQL"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></img></a>
2223
<a href="https://discord.gg/dU8eVGpJ8h"><img src="https://img.shields.io/badge/Discord-Join_us-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
2324
<a href="https://crates.io/crates/kite_sql/"><img src="https://img.shields.io/crates/v/kite_sql.svg"></a>

codecov.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
informational: true
6+
patch:
7+
default:
8+
informational: true
9+
10+
comment:
11+
layout: "diff, flags, files"
12+
behavior: default
13+
require_changes: false
14+
require_base: false
15+
require_head: true
16+
hide_project_coverage: false
17+
hide_comment_details: true

src/types/evaluator/binary.rs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ macro_rules! numeric_binary_evaluator_definition {
656656
};
657657
}
658658

659+
// GRCOV_EXCL_START
659660
#[cfg(all(test, not(target_arch = "wasm32")))]
660661
mod test {
661662
use super::*;
@@ -664,14 +665,38 @@ mod test {
664665
use crate::serdes::{ReferenceSerialization, ReferenceTables};
665666
use crate::storage::rocksdb::RocksTransaction;
666667
use crate::types::evaluator::BinaryEvaluatorRef;
668+
use crate::types::value::DataValue;
667669
use crate::types::LogicalType;
670+
use ordered_float::OrderedFloat;
671+
#[cfg(feature = "decimal")]
672+
use rust_decimal::Decimal;
668673
use std::borrow::Cow;
669674
use std::io::{Cursor, Seek, SeekFrom};
670675

671676
fn create(ty: LogicalType, op: BinaryOperator) -> Result<BinaryEvaluatorRef, DatabaseError> {
672677
binary_create(Cow::Owned(ty), op)
673678
}
674679

680+
fn eval(
681+
ty: LogicalType,
682+
op: BinaryOperator,
683+
left: &DataValue,
684+
right: &DataValue,
685+
) -> Result<DataValue, DatabaseError> {
686+
create(ty, op)?.binary_eval(left, right)
687+
}
688+
689+
fn assert_panics(f: impl FnOnce() + std::panic::UnwindSafe) {
690+
assert!(std::panic::catch_unwind(f).is_err());
691+
}
692+
693+
fn assert_binary_ref_panics(pos: u16, params: BinaryEvaluatorParams, value: &DataValue) {
694+
assert_panics(|| {
695+
let evaluator = BinaryEvaluatorRef::new(pos, params);
696+
let _ = evaluator.binary_eval(value, value).unwrap();
697+
});
698+
}
699+
675700
#[test]
676701
fn test_binary_evaluator_positions_are_stable() -> Result<(), DatabaseError> {
677702
assert_eq!(
@@ -716,4 +741,181 @@ mod test {
716741

717742
Ok(())
718743
}
744+
745+
#[test]
746+
fn test_binary_create_dispatches_numeric_time_and_decimal_evaluators(
747+
) -> Result<(), DatabaseError> {
748+
let numeric_cases = vec![
749+
(
750+
LogicalType::Tinyint,
751+
DataValue::Int8(1),
752+
DataValue::Int8(2),
753+
DataValue::Int8(3),
754+
),
755+
(
756+
LogicalType::Smallint,
757+
DataValue::Int16(1),
758+
DataValue::Int16(2),
759+
DataValue::Int16(3),
760+
),
761+
(
762+
LogicalType::Integer,
763+
DataValue::Int32(1),
764+
DataValue::Int32(2),
765+
DataValue::Int32(3),
766+
),
767+
(
768+
LogicalType::Bigint,
769+
DataValue::Int64(1),
770+
DataValue::Int64(2),
771+
DataValue::Int64(3),
772+
),
773+
(
774+
LogicalType::UTinyint,
775+
DataValue::UInt8(1),
776+
DataValue::UInt8(2),
777+
DataValue::UInt8(3),
778+
),
779+
(
780+
LogicalType::USmallint,
781+
DataValue::UInt16(1),
782+
DataValue::UInt16(2),
783+
DataValue::UInt16(3),
784+
),
785+
(
786+
LogicalType::UInteger,
787+
DataValue::UInt32(1),
788+
DataValue::UInt32(2),
789+
DataValue::UInt32(3),
790+
),
791+
(
792+
LogicalType::UBigint,
793+
DataValue::UInt64(1),
794+
DataValue::UInt64(2),
795+
DataValue::UInt64(3),
796+
),
797+
(
798+
LogicalType::Float,
799+
DataValue::Float32(OrderedFloat(1.0)),
800+
DataValue::Float32(2.0.into()),
801+
DataValue::Float32(3.0.into()),
802+
),
803+
(
804+
LogicalType::Double,
805+
DataValue::Float64(OrderedFloat(1.0)),
806+
DataValue::Float64(2.0.into()),
807+
DataValue::Float64(3.0.into()),
808+
),
809+
];
810+
811+
for (ty, left, right, expected) in numeric_cases {
812+
assert_eq!(eval(ty, BinaryOperator::Plus, &left, &right)?, expected);
813+
}
814+
815+
assert_eq!(
816+
eval(
817+
LogicalType::DateTime,
818+
BinaryOperator::Eq,
819+
&DataValue::Date64(1),
820+
&DataValue::Date64(1),
821+
)?,
822+
DataValue::Boolean(true)
823+
);
824+
assert_eq!(
825+
eval(
826+
LogicalType::Time(Some(0)),
827+
BinaryOperator::Plus,
828+
&DataValue::Time32(DataValue::pack_time(1, 0, 0), 0),
829+
&DataValue::Time32(DataValue::pack_time(2, 0, 0), 0),
830+
)?,
831+
DataValue::Time32(DataValue::pack_time(3, 0, 0), 0)
832+
);
833+
assert_eq!(
834+
eval(
835+
LogicalType::Time(Some(0)),
836+
BinaryOperator::Minus,
837+
&DataValue::Time32(DataValue::pack_time(3, 0, 0), 0),
838+
&DataValue::Time32(DataValue::pack_time(2, 0, 0), 0),
839+
)?,
840+
DataValue::Time32(DataValue::pack_time(1, 0, 0), 0)
841+
);
842+
assert_eq!(
843+
eval(
844+
LogicalType::TimeStamp(Some(0), false),
845+
BinaryOperator::NotEq,
846+
&DataValue::Time64(1, 0, false),
847+
&DataValue::Time64(2, 0, false),
848+
)?,
849+
DataValue::Boolean(true)
850+
);
851+
assert_eq!(
852+
eval(
853+
LogicalType::Boolean,
854+
BinaryOperator::NotEq,
855+
&DataValue::Boolean(true),
856+
&DataValue::Boolean(false),
857+
)?,
858+
DataValue::Boolean(true)
859+
);
860+
#[cfg(feature = "decimal")]
861+
assert_eq!(
862+
eval(
863+
LogicalType::Decimal(None, Some(1)),
864+
BinaryOperator::Plus,
865+
&DataValue::Decimal(Decimal::new(10, 1)),
866+
&DataValue::Decimal(Decimal::new(20, 1)),
867+
)?,
868+
DataValue::Decimal(Decimal::new(30, 1))
869+
);
870+
871+
Ok(())
872+
}
873+
874+
#[test]
875+
fn test_binary_create_rejects_unsupported_operators() {
876+
let cases = vec![
877+
(LogicalType::Integer, BinaryOperator::Like(None)),
878+
(LogicalType::Time(Some(0)), BinaryOperator::Multiply),
879+
(LogicalType::TimeStamp(Some(0), false), BinaryOperator::Plus),
880+
(LogicalType::Boolean, BinaryOperator::Plus),
881+
(
882+
LogicalType::Varchar(None, crate::types::CharLengthUnits::Characters),
883+
BinaryOperator::Plus,
884+
),
885+
(
886+
LogicalType::Tuple(vec![LogicalType::Integer]),
887+
BinaryOperator::Plus,
888+
),
889+
];
890+
891+
for (ty, op) in cases {
892+
assert!(matches!(
893+
create(ty, op),
894+
Err(DatabaseError::UnsupportedBinaryOperator(..))
895+
));
896+
}
897+
}
898+
899+
#[test]
900+
fn test_binary_eval_rejects_invalid_positions_and_params() {
901+
let value = DataValue::Utf8 {
902+
value: "abc".to_string(),
903+
ty: crate::types::value::Utf8Type::Variable(None),
904+
unit: crate::types::CharLengthUnits::Characters,
905+
};
906+
for offset in [UTF8_LIKE_OFFSET, UTF8_NOT_LIKE_OFFSET] {
907+
assert_binary_ref_panics(
908+
binary_pos(BINARY_UTF8_BASE, offset),
909+
BinaryEvaluatorParams::Unit,
910+
&value,
911+
);
912+
}
913+
assert_panics(|| {
914+
let evaluator = BinaryEvaluatorRef::new(u16::MAX, BinaryEvaluatorParams::Unit);
915+
let _ = evaluator
916+
.binary_eval(&DataValue::Int32(1), &DataValue::Int32(1))
917+
.unwrap();
918+
});
919+
}
719920
}
921+
// GRCOV_EXCL_STOP

0 commit comments

Comments
 (0)