From f8001ae2958a399eaf9b6dbf449e382cdd04d09b Mon Sep 17 00:00:00 2001 From: Ishan Grover <123376573+IshanGrover2004@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:44:47 +0530 Subject: [PATCH] [FEATURE] Create unit test for rust code (#1615) * feat: Add new function to allocate any object to heap with zero allocated * feat: Add unit tests for `decoder/commands.rs` * docs: Mention about PR in changelogs * feat: Add unit tests for `decoder/windows.rs` Refactor the code and use Default where needed Implement `PartialEq` also * fix: Intialise tmp extern C values for easy mocking * feat: Add unit tests for `decoder/timing.rs` * feat: Add unit tests for `decoder/output.rs` * feat: Add unit tests for `decoder/mod.rs` * feat: Add unit tests for `decoder/tv_screen.rs` * feat: Add unit tests for `lib.rs` * fix: Failing test * feat: [WIP] Add unit tests for `decoder/service_decoder.rs` * feat: Add unit tests for `decoder/service_decoder.rs` * feat: Add unit tests for `hardsubx/imgops.rs` * feat: Add unit tests for `hardsubx/utility.rs` * fix: cargo clippy * fix: doctest for `lib_ccxr` module * feat: Add test `lib_ccxr/util/mod.rs` * feat: Add test `lib_ccxr/util/levenshtein.rs` * feat: Add test `lib_ccxr/util/bits.rs` * feat: Add test `lib_ccxr/time/units.rs` * chore: Change function name * fix: Failing of missing values `tlt_config` * ci: Run unit test cases in `lib_ccxr` module also * ci: Run clippy & fmt in `lib_ccxr` module also * chore(clippy): Fix clippy warnings --- .github/workflows/format.yml | 13 +- .github/workflows/test_rust.yml | 4 + docs/CHANGES.TXT | 1 + src/rust/lib_ccxr/src/time/units.rs | 183 ++++++- src/rust/lib_ccxr/src/util/bits.rs | 50 +- src/rust/lib_ccxr/src/util/encoding.rs | 3 +- src/rust/lib_ccxr/src/util/levenshtein.rs | 34 ++ src/rust/lib_ccxr/src/util/mod.rs | 17 + src/rust/src/decoder/commands.rs | 38 ++ src/rust/src/decoder/mod.rs | 96 ++++ src/rust/src/decoder/output.rs | 34 ++ src/rust/src/decoder/service_decoder.rs | 553 +++++++++++++++++++++- src/rust/src/decoder/timing.rs | 166 +++++++ src/rust/src/decoder/tv_screen.rs | 108 +++++ src/rust/src/decoder/window.rs | 242 +++++++++- src/rust/src/hardsubx/imgops.rs | 63 +++ src/rust/src/hardsubx/utility.rs | 52 ++ src/rust/src/lib.rs | 61 +++ src/rust/src/utils.rs | 23 + 19 files changed, 1690 insertions(+), 51 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index f54201388..9f921024f 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -26,19 +26,22 @@ jobs: git diff-index --quiet HEAD -- || (git diff && exit 1) format_rust: runs-on: ubuntu-latest + strategy: + matrix: + workdir: ['./src/rust', './src/rust/lib_ccxr'] defaults: run: - working-directory: ./src/rust + working-directory: ${{ matrix.workdir }} steps: - uses: actions/checkout@v4 - name: cache uses: actions/cache@v4 with: path: | - src/rust/.cargo/registry - src/rust/.cargo/git - src/rust/target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + ${{ matrix.workdir }}/.cargo/registry + ${{ matrix.workdir }}/.cargo/git + ${{ matrix.workdir }}/target + key: ${{ runner.os }}-cargo-${{ hashFiles('${{ matrix.workdir }}/Cargo.lock') }} restore-keys: ${{ runner.os }}-cargo- - uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/test_rust.yml b/.github/workflows/test_rust.yml index 4791f821c..be73b8d62 100644 --- a/.github/workflows/test_rust.yml +++ b/.github/workflows/test_rust.yml @@ -26,6 +26,7 @@ jobs: src/rust/.cargo/registry src/rust/.cargo/git src/rust/target + src/rust/lib_ccxr/target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-cargo- - uses: actions-rs/toolchain@v1 @@ -35,3 +36,6 @@ jobs: - name: Test main module run: cargo test working-directory: ./src/rust + - name: Test lib_ccxr module + run: cargo test + working-directory: ./src/rust/lib_ccxr diff --git a/docs/CHANGES.TXT b/docs/CHANGES.TXT index a5cd21720..203039872 100644 --- a/docs/CHANGES.TXT +++ b/docs/CHANGES.TXT @@ -1,5 +1,6 @@ 1.0 (to be released) ----------------- +- New: Create unit test for rust code (#1615) - Breaking: Major argument flags revamp for CCExtractor (#1564 & #1619) - New: Create a Docker image to simplify the CCExtractor usage without any environmental hustle (#1611) - New: Add time units module in lib_ccxr (#1623) diff --git a/src/rust/lib_ccxr/src/time/units.rs b/src/rust/lib_ccxr/src/time/units.rs index 611806722..34d620827 100644 --- a/src/rust/lib_ccxr/src/time/units.rs +++ b/src/rust/lib_ccxr/src/time/units.rs @@ -45,7 +45,7 @@ pub enum TimestampFormat { /// /// # Examples /// ```rust - /// # use crate::time::units::{Timestamp, TimestampFormat}; + /// # use lib_ccxr::time::units::{Timestamp, TimestampFormat}; /// let timestamp = Timestamp::from_millis(6524365); /// let output = timestamp.to_formatted_time(TimestampFormat::None).unwrap(); /// assert_eq!(output, ""); @@ -56,7 +56,7 @@ pub enum TimestampFormat { /// /// # Examples /// ```rust - /// # use crate::time::units::{Timestamp, TimestampFormat}; + /// # use lib_ccxr::time::units::{Timestamp, TimestampFormat}; /// let timestamp = Timestamp::from_millis(6524365); /// let output = timestamp.to_formatted_time(TimestampFormat::HHMMSS).unwrap(); /// assert_eq!(output, "01:48:44"); @@ -67,7 +67,7 @@ pub enum TimestampFormat { /// /// # Examples /// ```rust - /// # use crate::time::units::{Timestamp, TimestampFormat}; + /// # use lib_ccxr::time::units::{Timestamp, TimestampFormat}; /// let timestamp = Timestamp::from_millis(6524365); /// let output = timestamp.to_formatted_time( /// TimestampFormat::Seconds { @@ -83,7 +83,7 @@ pub enum TimestampFormat { /// /// # Examples /// ```rust - /// # use crate::time::units::{Timestamp, TimestampFormat}; + /// # use lib_ccxr::time::units::{Timestamp, TimestampFormat}; /// // 11 March 2023 14:53:36.749 in UNIX timestamp. /// let timestamp = Timestamp::from_millis(1678546416749); /// let output = timestamp.to_formatted_time( @@ -99,7 +99,7 @@ pub enum TimestampFormat { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::{Timestamp, TimestampFormat}; + /// # use lib_ccxr::time::units::{Timestamp, TimestampFormat}; /// let timestamp = Timestamp::from_millis(6524365); /// let output = timestamp.to_formatted_time(TimestampFormat::HHMMSSFFF).unwrap(); /// assert_eq!(output, "01:48:44,365"); @@ -155,7 +155,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp;; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.millis(), 6524365); /// ``` @@ -167,7 +167,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp;; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.seconds(), 6524); /// ``` @@ -181,7 +181,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp;; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.as_sec_millis().unwrap(), (6524, 365)); /// ``` @@ -199,12 +199,12 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp;; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.as_hms_millis().unwrap(), (1, 48, 44, 365)); /// ``` /// ```rust - /// # use lib_ccxr::util::time::{Timestamp, TimestampError}; + /// # use lib_ccxr::time::units::{Timestamp, TimestampError}; /// let timestamp = Timestamp::from_millis(1678546416749); /// assert!(matches!( /// timestamp.as_hms_millis().unwrap_err(), @@ -227,7 +227,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// let mut output = String::new(); /// timestamp.write_srt_time(&mut output); @@ -243,7 +243,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// let mut output = String::new(); /// timestamp.write_vtt_time(&mut output); @@ -261,7 +261,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// let mut output = String::new(); /// timestamp.write_hms_millis_time(&mut output, ':'); @@ -283,7 +283,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// let mut output = String::new(); /// timestamp.write_ctime(&mut output); @@ -344,7 +344,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.to_srt_time().unwrap(), "01:48:44,365"); /// ``` @@ -358,7 +358,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.to_vtt_time().unwrap(), "01:48:44.365"); /// ``` @@ -374,7 +374,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.to_hms_millis_time(':').unwrap(), "01:48:44:365"); /// ``` @@ -388,7 +388,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::from_millis(6524365); /// assert_eq!(timestamp.to_ctime().unwrap(), "Thu Jan 01 01:48:44 1970"); /// ``` @@ -411,7 +411,7 @@ impl Timestamp { /// /// # Examples /// ```rust - /// # use lib_ccxr::util::time::Timestamp; + /// # use lib_ccxr::time::units::Timestamp; /// let timestamp = Timestamp::parse_optional_hhmmss_from_str("01:12:45").unwrap(); /// assert_eq!(timestamp, Timestamp::from_millis(4_365_000)); /// ``` @@ -643,3 +643,148 @@ impl GopTimeCode { ) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_timestamp_from_millis() { + let ts = Timestamp::from_millis(5000); + assert_eq!(ts.millis(), 5000); + assert_eq!(ts.seconds(), 5); + } + + #[test] + fn test_timestamp_from_hms_millis() { + let ts = Timestamp::from_hms_millis(1, 30, 45, 500).unwrap(); + assert_eq!(ts.millis(), 5445500); + + // Out of range case + assert!(matches!( + Timestamp::from_hms_millis(1, 60, 0, 0), + Err(TimestampError::InputOutOfRangeError) + )); + } + + #[test] + fn test_timestamp_as_sec_millis() { + let ts = Timestamp::from_millis(5445500); + assert_eq!(ts.as_sec_millis().unwrap(), (5445, 500)); + + let ts = Timestamp::from_millis(0); + assert_eq!(ts.as_sec_millis().unwrap(), (0, 0)); + + let ts = Timestamp::from_millis(1000); + assert_eq!(ts.as_sec_millis().unwrap(), (1, 0)); + + let ts = Timestamp::from_millis(-1000); + assert!(ts.as_sec_millis().is_err()); + } + + #[test] + fn test_timestamp_as_hms_millis() { + let ts = Timestamp::from_millis(5445500); + assert_eq!(ts.as_hms_millis().unwrap(), (1, 30, 45, 500)); + + let ts = Timestamp::from_millis(0); + assert_eq!(ts.as_hms_millis().unwrap(), (0, 0, 0, 0)); + + let ts = Timestamp::from_millis(3600000); + assert_eq!(ts.as_hms_millis().unwrap(), (1, 0, 0, 0)); + + let ts = Timestamp::from_millis(-1); + assert!(ts.as_hms_millis().is_err()); + } + + #[test] + fn test_timestamp_to_srt_time() { + let ts = Timestamp::from_millis(5445500); + assert_eq!(ts.to_srt_time().unwrap(), "01:30:45,500"); + + let ts = Timestamp::from_millis(0); + assert_eq!(ts.to_srt_time().unwrap(), "00:00:00,000"); + + let ts = Timestamp::from_millis(3661001); + assert_eq!(ts.to_srt_time().unwrap(), "01:01:01,001"); + + let ts = Timestamp::from_millis(-1); + assert!(ts.to_srt_time().is_err()); + } + + #[test] + fn test_timestamp_to_vtt_time() { + let ts = Timestamp::from_millis(5445500); + assert_eq!(ts.to_vtt_time().unwrap(), "01:30:45.500"); + + let ts = Timestamp::from_millis(0); + assert_eq!(ts.to_vtt_time().unwrap(), "00:00:00.000"); + + let ts = Timestamp::from_millis(3661001); + assert_eq!(ts.to_vtt_time().unwrap(), "01:01:01.001"); + + let ts = Timestamp::from_millis(-1); + assert!(ts.to_vtt_time().is_err()); + } + + #[test] + fn test_timestamp_to_hms_millis_time() { + let ts = Timestamp::from_millis(5445500); + assert_eq!(ts.to_hms_millis_time(':').unwrap(), "01:30:45:500"); + + let ts = Timestamp::from_millis(0); + assert_eq!(ts.to_hms_millis_time('.').unwrap(), "00:00:00.000"); + + let ts = Timestamp::from_millis(-3661001); + assert_eq!(ts.to_hms_millis_time(':').unwrap(), "-01:01:01:001"); + + let ts = Timestamp::from_millis(1); + assert_eq!(ts.to_hms_millis_time(':').unwrap(), "00:00:00:001"); + } + + #[test] + fn test_timestamp_to_ctime() { + let ts = Timestamp::from_millis(5445500); + assert_eq!(ts.to_ctime().unwrap(), "Thu Jan 01 01:30:45 1970"); + + let ts = Timestamp::from_millis(0); + assert_eq!(ts.to_ctime().unwrap(), "Thu Jan 01 00:00:00 1970"); + + let ts = Timestamp::from_millis(31536000000); // 1 year later + assert_eq!(ts.to_ctime().unwrap(), "Fri Jan 01 00:00:00 1971"); + + let ts = Timestamp::from_millis(-1); + assert!(ts.to_ctime().is_err()); + } + + #[test] + fn test_timestamp_parse_optional_hhmmss_from_str() { + assert_eq!( + Timestamp::parse_optional_hhmmss_from_str("01:30:45").unwrap(), + Timestamp::from_millis(5445000) + ); + assert_eq!( + Timestamp::parse_optional_hhmmss_from_str("30:45").unwrap(), + Timestamp::from_millis(1845000) + ); + + // Error cases + assert!(matches!( + Timestamp::parse_optional_hhmmss_from_str("01:60:00"), + Err(TimestampError::InputOutOfRangeError) + )); + assert!(matches!( + Timestamp::parse_optional_hhmmss_from_str("01:30:45:00"), + Err(TimestampError::ParsingError) + )); + } + + #[test] + fn test_timestamp_arithmetic() { + let ts1 = Timestamp::from_millis(5000); + let ts2 = Timestamp::from_millis(3000); + assert_eq!((ts1 + ts2).millis(), 8000); + assert_eq!((ts1 - ts2).millis(), 2000); + assert_eq!((-ts1).millis(), -5000); + } +} diff --git a/src/rust/lib_ccxr/src/util/bits.rs b/src/rust/lib_ccxr/src/util/bits.rs index 0127e6a0d..6a7ca4bfa 100644 --- a/src/rust/lib_ccxr/src/util/bits.rs +++ b/src/rust/lib_ccxr/src/util/bits.rs @@ -217,8 +217,54 @@ pub fn get_crc32_byte(value: u8) -> u32 { pub fn verify_crc32(buf: &[u8]) -> bool { let mut crc: i32 = -1; for &byte in buf { - let expr = ((crc >> 24) ^ (byte & 0xff) as i32) & 0xff; - crc = (crc << 8) ^ get_crc32_byte(expr as u8) as i32; + let expr = (crc >> 24) as u8 ^ byte; + crc = (crc << 8) ^ get_crc32_byte(expr) as i32; } crc == 0 } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_parity() { + assert_eq!(get_parity(0), false); + assert_eq!(get_parity(1), true); + assert_eq!(get_parity(128), true); + assert_eq!(get_parity(255), false); + } + + #[test] + fn test_get_reverse_byte() { + assert_eq!(get_reverse_byte(0), 0x00); + assert_eq!(get_reverse_byte(1), 0x80); + assert_eq!(get_reverse_byte(255), 0xFF); + assert_eq!(get_reverse_byte(0b10101010), 0b01010101); + } + + #[test] + fn test_decode_hamming_8_4() { + assert_eq!(decode_hamming_8_4(0x00), Some(0x01)); + assert_eq!(decode_hamming_8_4(0x01), None); + assert_eq!(decode_hamming_8_4(0xFF), Some(0x0e)); + } + + #[test] + fn test_decode_hamming_24_18() { + assert_eq!(decode_hamming_24_18(0x00000000), Some(0x00000000)); + assert_eq!(decode_hamming_24_18(0x00000001), None); + assert_eq!(decode_hamming_24_18(0xFFFFFFFF), Some(0x003FFFF)); + assert_eq!( + decode_hamming_24_18(0b101010101010101010101010), + Some(0b10101001010100100) + ); + } + + #[test] + fn test_get_crc32_byte() { + assert_eq!(get_crc32_byte(0), 0x00000000); + assert_eq!(get_crc32_byte(1), 0x04c11db7); + assert_eq!(get_crc32_byte(255), 0xb1f740b4); + } +} diff --git a/src/rust/lib_ccxr/src/util/encoding.rs b/src/rust/lib_ccxr/src/util/encoding.rs index e3f48f0e4..5b8e76d53 100644 --- a/src/rust/lib_ccxr/src/util/encoding.rs +++ b/src/rust/lib_ccxr/src/util/encoding.rs @@ -647,8 +647,7 @@ fn latin1_to_line21(c: Latin1Char) -> Line21Char { 0xe5 => 0xc9, // Lowercase A, ring 0xd8 => 0xca, // Uppercase O, slash 0xf8 => 0xcb, // Lowercase o, slash - 0x00..=0x29 | 0x2b..=0x5b | 0x5d => c as Line21Char, - 0x5c..=0x7a => c as Line21Char, + 0x00..=0x29 | 0x2b..=0x5b | 0x5d..=0x7a => c as Line21Char, _ => UNAVAILABLE_CHAR, } } diff --git a/src/rust/lib_ccxr/src/util/levenshtein.rs b/src/rust/lib_ccxr/src/util/levenshtein.rs index 3b23b6267..33c9391ec 100644 --- a/src/rust/lib_ccxr/src/util/levenshtein.rs +++ b/src/rust/lib_ccxr/src/util/levenshtein.rs @@ -37,3 +37,37 @@ pub fn levenshtein_dist(s1: &[u64], s2: &[u64]) -> usize { pub fn levenshtein_dist_char(s1: &[T], s2: &[T]) -> usize { levenshtein(s1, s2) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_levenshtein() { + // Empty slices + assert_eq!(levenshtein(&[] as &[i32; 0], &[] as &[i32; 0]), 0); + + // Integers + assert_eq!(levenshtein(&[1, 2, 3, 4, 5], &[1, 3, 2, 4, 5, 6]), 3); + assert_eq!(levenshtein(&[1, 2, 3], &[1, 2, 3]), 0); + assert_eq!(levenshtein(&[], &[1, 2, 3]), 3); + assert_eq!(levenshtein(&[1, 2, 3], &[]), 3); + + // Characters + assert_eq!(levenshtein(&['a', 'b', 'c'], &['a', 'd', 'c']), 1); + assert_eq!( + levenshtein( + &['k', 'i', 't', 't', 'e', 'n'], + &['s', 'i', 't', 't', 'i', 'n', 'g'] + ), + 3 + ); + + //Strings or &str + assert_eq!( + levenshtein(&["hello", "world"], &["hello", "rust", "world"]), + 1 + ); + assert_eq!(levenshtein(&["foo", "bar", "baz"], &["foo", "baz"]), 1); + } +} diff --git a/src/rust/lib_ccxr/src/util/mod.rs b/src/rust/lib_ccxr/src/util/mod.rs index cb62df05e..12a8625fd 100644 --- a/src/rust/lib_ccxr/src/util/mod.rs +++ b/src/rust/lib_ccxr/src/util/mod.rs @@ -18,3 +18,20 @@ pub fn write_string_into_pointer(buffer: *mut c_char, string: &str) { buffer[..string.len()].copy_from_slice(string.as_bytes()); buffer[string.len()] = b'\0'; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_write_string_into_pointer() { + let test_string = "CCExtractor is the best"; + let mut buffer = vec![0u8; test_string.len() + 1]; + let buffer_ptr = buffer.as_mut_ptr() as *mut c_char; + + write_string_into_pointer(buffer_ptr, test_string); + + assert_eq!(&buffer[..test_string.len()], test_string.as_bytes()); + assert_eq!(buffer[test_string.len()], 0); // Check null terminator + } +} diff --git a/src/rust/src/decoder/commands.rs b/src/rust/src/decoder/commands.rs index 4799d1a3d..1eb7f553a 100644 --- a/src/rust/src/decoder/commands.rs +++ b/src/rust/src/decoder/commands.rs @@ -184,3 +184,41 @@ pub fn handle_C3(code: u8, next_code: u8) -> u8 { } } } + +#[cfg(test)] +mod test { + use super::{handle_C2, handle_C3}; + + #[test] + fn test_handle_C2() { + // Case 1: Single-byte control bytes + assert_eq!(handle_C2(0), 1); + assert_eq!(handle_C2(7), 1); + + // Case 2: two-byte control codes + assert_eq!(handle_C2(8), 2); + assert_eq!(handle_C2(15), 2); + + // Case 3: three-byte control codes + assert_eq!(handle_C2(16), 3); + assert_eq!(handle_C2(23), 3); + + // Case 4: four-byte control codes + assert_eq!(handle_C2(34), 4); + } + + #[test] + fn test_handle_C3() { + // Case 1: Five-byte control bytes + assert_eq!(handle_C3(128, 1), 5); + assert_eq!(handle_C3(135, 1), 5); + + // Case 2: Six-byte control codes + assert_eq!(handle_C3(136, 1), 6); + assert_eq!(handle_C3(143, 1), 6); + + // Case 3: variable length commands + assert_eq!(handle_C3(149, 4), 6); + assert_eq!(handle_C3(155, 9), 11); + } +} diff --git a/src/rust/src/decoder/mod.rs b/src/rust/src/decoder/mod.rs index 2ee5c26bc..875012fdc 100644 --- a/src/rust/src/decoder/mod.rs +++ b/src/rust/src/decoder/mod.rs @@ -224,3 +224,99 @@ impl Default for dtvcc_symbol { Self { sym: 0, init: 0 } } } + +impl PartialEq for dtvcc_symbol { + fn eq(&self, other: &Self) -> bool { + self.sym == other.sym && self.init == other.init + } +} + +#[cfg(test)] +mod test { + use crate::utils::get_zero_allocated_obj; + + use super::*; + + #[test] + fn test_process_cc_data() { + let mut dtvcc_ctx = get_zero_allocated_obj::(); + let mut decoder = Dtvcc::new(&mut dtvcc_ctx); + + // Case 1: cc_type = 2 + let mut dtvcc_report = ccx_decoder_dtvcc_report::default(); + decoder.report = &mut dtvcc_report; + decoder.is_header_parsed = true; + decoder.is_active = true; + decoder.report_enabled = true; + decoder.packet = vec![0xC2, 0x23, 0x45, 0x67, 0x00, 0x00]; + decoder.packet_length = 4; + + decoder.process_cc_data(1, 2, 0x01, 0x02); + + assert_eq!(decoder.report.services[1], 1); + assert!(decoder.packet.iter().all(|&ele| ele == 0)); + assert_eq!(decoder.packet_length, 0); + + // Case 2: cc_type = 3 with `is_header_parsed = true` + decoder.is_header_parsed = true; + decoder.packet = vec![0xC2, 0x23, 0x45, 0x67, 0x00, 0x00]; + decoder.packet_length = 4; + + decoder.process_cc_data(1, 3, 0x01, 0x02); + + assert_eq!(decoder.packet[0], 0x01); + assert_eq!(decoder.packet[1], 0x02); + assert_eq!(decoder.packet_length, 2); + + // Case 3: cc_type = 3 with `is_header_parsed = false` + decoder.is_header_parsed = false; + decoder.packet = vec![0xC2, 0x23, 0x45, 0x67, 0x00, 0x00]; + decoder.packet_length = 4; + + decoder.process_cc_data(1, 3, 0x01, 0x02); + + assert_eq!(decoder.packet, vec![0xC2, 0x23, 0x45, 0x67, 0x01, 0x02]); + assert_eq!(decoder.packet_length, 6); + assert_eq!(decoder.is_header_parsed, true); + } + + #[test] + fn test_process_current_packet() { + let mut dtvcc_ctx = get_zero_allocated_obj::(); + let mut decoder = Dtvcc::new(&mut dtvcc_ctx); + + // Case 1: Without providing last_sequence + let mut dtvcc_report = ccx_decoder_dtvcc_report::default(); + decoder.report = &mut dtvcc_report; + decoder.packet = vec![0xC2, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; + decoder.packet_length = 8; + decoder.process_current_packet(4); + + assert_eq!(decoder.report.services[1], 1); + assert_eq!(decoder.packet_length, 0); // due to `clear_packet()` fn call + + // Case 2: With providing last_sequence + let mut dtvcc_report = ccx_decoder_dtvcc_report::default(); + decoder.report = &mut dtvcc_report; + decoder.packet = vec![0xC7, 0xC2, 0x12, 0x67, 0x29, 0xAB, 0xCD, 0xEF]; + decoder.packet_length = 8; + decoder.last_sequence = 6; + decoder.process_current_packet(4); + + assert_eq!(decoder.report.services[6], 1); + assert_eq!(decoder.packet_length, 0); // due to `clear_packet()` fn call + + // Test case 3: Packet with extended header and multiple service blocks + let mut dtvcc_report = ccx_decoder_dtvcc_report::default(); + decoder.report = &mut dtvcc_report; + decoder.packet = vec![ + 0xC0, 0xE7, 0x08, 0x02, 0x01, 0x02, 0x07, 0x03, 0x03, 0x04, 0x05, + ]; + decoder.packet_length = 11; + decoder.last_sequence = 6; + decoder.process_current_packet(6); + + assert_eq!(decoder.report.services[8], 1); + assert_eq!(decoder.packet_length, 0); // due to `clear_packet()` fn call + } +} diff --git a/src/rust/src/decoder/output.rs b/src/rust/src/decoder/output.rs index fd0c96dc3..1dad8c149 100644 --- a/src/rust/src/decoder/output.rs +++ b/src/rust/src/decoder/output.rs @@ -112,3 +112,37 @@ pub fn color_to_hex(color: u8) -> (u8, u8, u8) { ); (red, green, blue) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_write_char() { + let mut buf = Vec::new(); + + // Write 8-bit symbol + let sym = dtvcc_symbol { sym: 0x41, init: 0 }; + write_char(&sym, &mut buf); + assert_eq!(buf, vec![0x41]); + + buf.clear(); + + // Write 16-bit symbol + let sym = dtvcc_symbol { + sym: 0x1234, + init: 0, + }; + write_char(&sym, &mut buf); + assert_eq!(buf, vec![0x12, 0x34]); + } + + #[test] + fn test_color_to_hex() { + assert_eq!(color_to_hex(0b00_00_00), (0, 0, 0)); // Black + assert_eq!(color_to_hex(0b11_11_11), (3, 3, 3)); // White + assert_eq!(color_to_hex(0b10_01_00), (2, 1, 0)); // Red + assert_eq!(color_to_hex(0b01_10_01), (1, 2, 1)); // Green + assert_eq!(color_to_hex(0b00_11_10), (0, 3, 2)); // Blue + } +} diff --git a/src/rust/src/decoder/service_decoder.rs b/src/rust/src/decoder/service_decoder.rs index 5e86cbc22..39d0a05dc 100644 --- a/src/rust/src/decoder/service_decoder.rs +++ b/src/rust/src/decoder/service_decoder.rs @@ -1134,12 +1134,10 @@ impl dtvcc_service_decoder { } window.is_empty = 0; + // Add symbol to window - unsafe { - window.rows[window.pen_row as usize] - .add(window.pen_column as usize) - .write(sym); - } + window.rows[window.pen_row as usize] = Box::into_raw(Box::new(sym)); + // "Painting" char by pen - attribs window.pen_attribs[window.pen_row as usize][window.pen_column as usize] = window.pen_attribs_pattern; @@ -1221,3 +1219,548 @@ extern "C" fn ccxr_flush_decoder(dtvcc: *mut dtvcc_ctx, decoder: *mut dtvcc_serv } decoder.flush(encoder); } + +#[cfg(test)] +mod test { + use super::*; + use crate::utils::get_zero_allocated_obj; + + // -------------------------- C0 Commands------------------------- + #[test] + fn test_process_cr() { + let set_tmp_values = |window: &mut dtvcc_window| { + window.is_defined = 1; + window.visible = 1; + window.row_count = 5; + window.col_count = 10; + window.pen_row = 2; + window.pen_column = 5; + window.is_empty = 0; + }; + + let mut decoder = get_zero_allocated_obj::(); + decoder.current_window = 0; + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32; + + let mut encoder = encoder_ctx::default(); + let mut timing = ccx_common_timing_ctx::default(); + let no_rollup = false; + + // Set temp values of window + set_tmp_values(&mut decoder.windows[0]); + + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32; + decoder.process_cr(&mut encoder, &mut timing, no_rollup); + assert_eq!(decoder.windows[0].pen_row, 3); + assert_eq!(decoder.windows[0].pen_column, 0); + + // Set temp values of window + set_tmp_values(&mut decoder.windows[0]); + + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT as i32; + decoder.process_cr(&mut encoder, &mut timing, no_rollup); + assert_eq!(decoder.windows[0].pen_row, 3); + assert_eq!(decoder.windows[0].pen_column, 10); + + // Set temp values of window + set_tmp_values(&mut decoder.windows[0]); + + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM as i32; + decoder.process_cr(&mut encoder, &mut timing, no_rollup); + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 6); + + // Set temp values of window + set_tmp_values(&mut decoder.windows[0]); + + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP as i32; + decoder.process_cr(&mut encoder, &mut timing, no_rollup); + assert_eq!(decoder.windows[0].pen_row, 5); + assert_eq!(decoder.windows[0].pen_column, 6); + } + + #[test] + fn test_process_hcr() { + let mut decoder = get_zero_allocated_obj::(); + decoder.current_window = 1; + decoder.windows[1].pen_column = 12; + decoder.windows[1].pen_row = 1; + decoder.windows[1].rows[1] = Box::into_raw(Box::new(dtvcc_symbol::new(1))); + decoder.windows[1].rows[2] = Box::into_raw(Box::new(dtvcc_symbol::new(1))); + decoder.windows[1].memory_reserved = 1; + + decoder.process_hcr(); + + assert_eq!(decoder.windows[1].pen_column, 0); + + // Ensuring, it erases all text on the row mentioned by `pen_row` + assert_eq!( + unsafe { decoder.windows[1].rows[1].as_mut() }, + Some(&mut dtvcc_symbol::default()), + ); + // Do not clear text for row which is not mentioned by `pen_row` + assert_eq!( + unsafe { decoder.windows[1].rows[2].as_mut() }, + Some(&mut dtvcc_symbol { sym: 1, init: 1 }), + ); + } + + #[test] + fn test_process_ff() { + let mut decoder = get_zero_allocated_obj::(); + decoder.current_window = 1; + decoder.windows[1].pen_column = 2; + decoder.windows[1].pen_row = 1; + decoder.windows[1].memory_reserved = 1; + decoder.windows[1].rows[1] = Box::into_raw(Box::new(dtvcc_symbol::new(1))); + decoder.windows[1].rows[2] = Box::into_raw(Box::new(dtvcc_symbol::new(1))); + + decoder.process_ff(); + + assert_eq!(decoder.windows[1].pen_column, 0); + assert_eq!(decoder.windows[1].pen_row, 0); + + // Ensuring, it erases all text on the rows + // (Doesn't matter for value of pen_column or pen_row..Just delete all text present) + assert_eq!( + unsafe { decoder.windows[1].rows[1].as_mut() }, + Some(&mut dtvcc_symbol::default()), + ); + assert_eq!( + unsafe { decoder.windows[1].rows[2].as_mut() }, + Some(&mut dtvcc_symbol::default()), + ); + } + + #[test] + fn test_process_bs() { + let mut decoder = get_zero_allocated_obj::(); + decoder.current_window = 1; + + // 0 -> dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT + decoder.windows[1].attribs.print_direction = 0; + decoder.windows[1].pen_column = 3; // > 0 + + decoder.process_bs(); + assert_eq!(decoder.windows[1].pen_column, 2); + + // 1 -> dtvcc_window_pd::DTVCC_WINDOW_PD_RIGHT_LEFT + decoder.windows[1].attribs.print_direction = 1; + decoder.windows[1].pen_column = 3; + decoder.windows[1].col_count = 5; + + decoder.process_bs(); + assert_eq!(decoder.windows[1].pen_column, 4); + + // 2 -> dtvcc_window_pd::DTVCC_WINDOW_PD_TOP_BOTTOM + decoder.windows[1].attribs.print_direction = 2; + decoder.windows[1].pen_row = 3; + + decoder.process_bs(); + assert_eq!(decoder.windows[1].pen_row, 2); + + // 3 -> dtvcc_window_pd::DTVCC_WINDOW_PD_BOTTOM_TOP + decoder.windows[1].attribs.print_direction = 3; + decoder.windows[1].pen_row = 3; + decoder.windows[1].row_count = 5; + + decoder.process_bs(); + assert_eq!(decoder.windows[1].pen_column, 4); + + // 4..infinite -> Invalid print direction + decoder.windows[1].attribs.print_direction = 4; + } + + #[test] + fn test_process_p16() { + let mut decoder = get_zero_allocated_obj::(); + decoder.current_window = 0; + decoder.windows[0].is_defined = 1; + decoder.windows[0].row_count = 4; + decoder.windows[0].col_count = 4; + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32; + let block = ['a' as u8, 'b' as u8] as [c_uchar; 2]; + + decoder.process_p16(&block); + + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 1); + unsafe { + assert_eq!( + *decoder.windows[0].rows[0], + dtvcc_symbol::new_16(block[0], block[1]) + ); + } + } + + // -------------------------- C1 Commands------------------------- + #[test] + fn test_handle_display_windows() { + let mut decoder = get_zero_allocated_obj::(); + let mut timing = ccx_common_timing_ctx::default(); + + decoder.windows[0].is_defined = 1; + decoder.windows[2].is_defined = 1; + decoder.windows[2].visible = 1; // Window 2 is already visible + + // Test case 1: Display all windows + let windows_bitmap = 0b00000111; + decoder.handle_display_windows(windows_bitmap, &mut timing); + + assert_eq!(decoder.windows[0].visible, 1); + assert_eq!(decoder.windows[1].visible, 0); + assert_eq!(decoder.windows[2].visible, 1); + + // Test case 2: Do nothing with the windows + let windows_bitmap = 0b00000000; + decoder.windows[1].is_defined = 1; + decoder.handle_display_windows(windows_bitmap, &mut timing); + + assert_eq!(decoder.windows[0].visible, 1); + // Even after window is defined & `windows_bitmap = 0` => it is not visible + assert_eq!(decoder.windows[1].visible, 0); + assert_eq!(decoder.windows[2].visible, 1); + } + + #[test] + fn test_handle_define_windows() { + let mut decoder = get_zero_allocated_obj::(); + let mut timing = ccx_common_timing_ctx::default(); + let window_id = 1; + let test_block = [ + 0b00000111, 0b01000000, 0b00000010, 0b00001010, 0b00111111, 0b00001000, + ]; + + decoder.handle_define_windows(window_id, &test_block, &mut timing); + + let window = &decoder.windows[window_id as usize]; + + assert_eq!(window.number, window_id as i32); + assert_eq!(window.priority, 0x7); + assert_eq!(window.col_lock, 0x0); + assert_eq!(window.row_lock, 0x0); + assert_eq!(window.visible, 0x0); + assert_eq!(window.anchor_vertical, 64); + assert_eq!(window.relative_pos, 0x0); + assert_eq!(window.anchor_horizontal, 0x2); + assert_eq!(window.row_count, 0xb); + assert_eq!(window.anchor_point, 0x0); + assert_eq!(window.col_count, 64); + assert_eq!(window.pen_style, 0x1); + assert_eq!(window.win_style, 0x1); + assert_eq!(window.pen_row, 0x0); + assert_eq!(window.pen_column, 0x0); + assert_eq!(window.is_defined, 1); + assert_eq!(window.memory_reserved, 1); + + // Check `Command` has been set or not + assert_eq!(window.commands[0], 0b00000111); + assert_eq!(window.commands[1], 0b01000000); + assert_eq!(window.commands[2], 0b00000010); + assert_eq!(window.commands[3], 0b00001010); + assert_eq!(window.commands[4], 0b00111111); + assert_eq!(window.commands[5], 0b00001000); + } + + #[test] + fn test_handle_set_pen_attributes() { + let mut decoder = get_zero_allocated_obj::(); + let test_block = [0b00111000, 0b11010001]; + + decoder.current_window = 0; + decoder.windows[0].pen_row = 0; + + decoder.handle_set_pen_attributes(&test_block); + + let pen = &decoder.windows[0].pen_attribs_pattern; + assert_eq!(pen.pen_size, 0x0); + assert_eq!(pen.offset, 0x2); + assert_eq!(pen.text_tag, 0x3); + assert_eq!(pen.font_tag, 0x1); + assert_eq!(pen.edge_type, 0x2); + assert_eq!(pen.underline, 0x1); + assert_eq!(pen.italic, 0x1); + } + + #[test] + fn test_handle_set_pen_color() { + let mut decoder = get_zero_allocated_obj::(); + let test_block = [0b00111111, 0b00111110, 0b00111100]; + + decoder.current_window = 0; + decoder.windows[0].pen_row = 0; + + decoder.handle_set_pen_color(&test_block); + + let color = &decoder.windows[0].pen_color_pattern; + assert_eq!(color.fg_color, 0x3f); + assert_eq!(color.fg_opacity, 0x0); + assert_eq!(color.bg_color, 0x3e); + assert_eq!(color.bg_opacity, 0x0); + assert_eq!(color.edge_color, 0x3c); + } + + #[test] + fn test_handle_set_pen_location() { + let mut decoder = get_zero_allocated_obj::(); + let test_block = [0b00001111, 0b00111111]; + decoder.current_window = 0; + + decoder.handle_set_pen_location(&test_block); + + assert_eq!(decoder.windows[0].pen_row, 0xf); + assert_eq!(decoder.windows[0].pen_column, 0x3f); + } + + #[test] + fn test_handle_set_window_attributes() { + let mut decoder = get_zero_allocated_obj::(); + decoder.current_window = 0; + let test_block = [ + 0b00111111, // fill_color, fill_opacity + 0b00111110, // border_color, border_type01 + 0b01010101, // justify, scroll_dir, print_dir, word_wrap, border_type + 0b01010101, // display_eff, effect_dir, effect_speed + ]; + + decoder.handle_set_window_attributes(&test_block); + + let window_attribs = &decoder.windows[0].attribs; + + assert_eq!(window_attribs.fill_color, 0x3f); + assert_eq!(window_attribs.fill_opacity, 0x0); + assert_eq!(window_attribs.border_color, 0x3e); + assert_eq!(window_attribs.border_type, 0x0); + assert_eq!(window_attribs.justify, 0x1); + assert_eq!(window_attribs.scroll_direction, 0x1); + assert_eq!(window_attribs.print_direction, 0x1); + assert_eq!(window_attribs.word_wrap, 0x1); + assert_eq!(window_attribs.display_effect, 0x1); + assert_eq!(window_attribs.effect_direction, 0x1); + assert_eq!(window_attribs.effect_speed, 0x5); + } + + #[test] + fn test_handle_set_current_window() { + let mut decoder = get_zero_allocated_obj::(); + + let window_id = 2; + decoder.windows[window_id].is_defined = 1; + decoder.current_window = 5; + + decoder.handle_set_current_window(window_id as u8); + + assert_eq!(decoder.current_window, window_id as i32); + } + + #[test] + fn test_handle_reset() { + let mut decoder = get_zero_allocated_obj::(); + + // Set random values in decoder + decoder.current_window = 0; + decoder.windows.iter_mut().for_each(|window| { + window.visible = 1; + window.is_defined = 1; + window.visible = 1; + window.commands.fill(1); + }); + decoder.tv = Box::into_raw(get_zero_allocated_obj::()); + + decoder.handle_reset(); + + // Test if reset perfectly works or not? + assert_eq!(decoder.current_window, -1); + decoder.windows.iter_mut().for_each(|window| { + assert_eq!(window.visible, 0); + assert_eq!(window.is_defined, 0); + assert!(window.commands.iter().all(|&c| c == 0)); + }); + } + + #[test] + fn test_is_window_overlapping() { + let mut decoder = get_zero_allocated_obj::(); + + // Non-overlapping windows + decoder.windows[0].is_defined = 1; + decoder.windows[0].visible = 1; + decoder.windows[0].anchor_vertical = 2; + decoder.windows[0].anchor_horizontal = 2; + decoder.windows[0].row_count = 5; + decoder.windows[0].col_count = 10; + assert!(!decoder.is_window_overlapping(&decoder.windows[0])); + + decoder.windows[1].is_defined = 1; + decoder.windows[1].visible = 1; + decoder.windows[1].anchor_vertical = 10; + decoder.windows[1].anchor_horizontal = 10; + decoder.windows[1].row_count = 3; + decoder.windows[1].col_count = 5; + assert!(!decoder.is_window_overlapping(&decoder.windows[1])); + + // Overlapping windows + decoder.windows[2].is_defined = 1; + decoder.windows[2].visible = 1; + decoder.windows[2].anchor_vertical = 3; + decoder.windows[2].anchor_horizontal = 3; + decoder.windows[2].row_count = 3; + decoder.windows[2].col_count = 5; + decoder.windows[2].priority = 1; + assert!(decoder.is_window_overlapping(&decoder.windows[2])); + + decoder.windows[3].is_defined = 1; + decoder.windows[3].visible = 1; + decoder.windows[3].anchor_vertical = 4; + decoder.windows[3].anchor_horizontal = 4; + decoder.windows[3].row_count = 3; + decoder.windows[3].col_count = 5; + decoder.windows[3].priority = 2; + assert!(decoder.is_window_overlapping(&decoder.windows[3])); + } + + #[test] + fn test_has_visible_windows() { + let mut decoder = get_zero_allocated_obj::(); + + // Default case - No windows is visible + assert!(!decoder.has_visible_windows()); + + // Make 1 window visible + decoder.windows[0].visible = 1; + assert!(decoder.has_visible_windows()); + } + + // -------------------------- G0, G1 and extended Commands------------------------- + #[test] + fn test_handle_G0() { + let mut decoder = get_zero_allocated_obj::(); + + decoder.current_window = 0; + decoder.windows[0].is_defined = 1; + decoder.windows[0].row_count = 4; + decoder.windows[0].col_count = 4; + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32; + + // Case: block[0] == 0x7F + let block = [0x7F, 0x61]; + decoder.handle_G0(&block); + + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 1); + unsafe { + assert_eq!( + decoder.windows[0].rows[0].read().sym, + CCX_DTVCC_MUSICAL_NOTE_CHAR + ); + } + + // Case: block[0] != 0x7F + let block = [0x60, 0x61]; + let return_value = decoder.handle_G0(&block); + + assert_eq!(return_value, 1); + unsafe { + assert_eq!(decoder.windows[0].rows[0].read().sym, 96); + } + } + + #[test] + fn test_handle_G1() { + let mut decoder = get_zero_allocated_obj::(); + + decoder.current_window = 0; + decoder.windows[0].is_defined = 1; + decoder.windows[0].row_count = 4; + decoder.windows[0].col_count = 4; + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32; + + let block = [0x7F, 0x61]; + let return_value = decoder.handle_G1(&block); + + assert_eq!(return_value, 1); + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 1); + unsafe { + assert_eq!(decoder.windows[0].rows[0].read().sym, 0x7F); + } + } + + #[test] + fn test_handle_extended_char() { + let mut decoder = get_zero_allocated_obj::(); + + decoder.current_window = 0; + decoder.windows[0].is_defined = 1; + decoder.windows[0].row_count = 4; + decoder.windows[0].col_count = 4; + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32; + + // 0..=0x1F + let return_value = decoder.handle_extended_char(&[0x1A, 0x61]); + assert_eq!(return_value, 4); + + // 0x20..=0x7F + let return_value = decoder.handle_extended_char(&[0x25, 0x61]); + assert_eq!(return_value, 1); + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 1); + unsafe { + assert_eq!(decoder.windows[0].rows[0].read().sym, 0x5); + } + + // 0x80..=0x9F + let return_value = decoder.handle_extended_char(&[0x86, 0x61]); + assert_eq!(return_value, 5); + + // Anyother value > 0x9F (59) + let return_value = decoder.handle_extended_char(&[65, 0x61]); + assert_eq!(return_value, 1); + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 2); + unsafe { + assert_eq!(decoder.windows[0].rows[0].read().sym, 0x20); + } + } + + #[test] + fn test_process_character() { + let mut decoder = get_zero_allocated_obj::(); + let sym = dtvcc_symbol::new(0x41); + + // Undefined window case + decoder.windows[0].is_defined = 0; + decoder.process_character(sym); + + // No changes occurred + assert!(decoder.windows[0].rows[0].is_null()); + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 0); + + // Valid window case + decoder.current_window = 0; + decoder.windows[0].is_defined = 1; + decoder.windows[0].row_count = 4; + decoder.windows[0].col_count = 4; + decoder.windows[0].attribs.print_direction = + dtvcc_window_pd::DTVCC_WINDOW_PD_LEFT_RIGHT as i32; + + decoder.process_character(sym); + + // Check changes + assert_eq!(decoder.windows[0].pen_row, 0); + assert_eq!(decoder.windows[0].pen_column, 1); + unsafe { + assert_eq!(decoder.windows[0].rows[0].read(), dtvcc_symbol::new(0x41)); + } + } +} diff --git a/src/rust/src/decoder/timing.rs b/src/rust/src/decoder/timing.rs index 5702359ef..6bd5609ff 100644 --- a/src/rust/src/decoder/timing.rs +++ b/src/rust/src/decoder/timing.rs @@ -65,6 +65,16 @@ impl ccx_boundary_time { } } +impl PartialEq for ccx_boundary_time { + fn eq(&self, other: &Self) -> bool { + self.hh == other.hh + && self.mm == other.mm + && self.ss == other.ss + && self.time_in_ms == other.time_in_ms + && self.set == other.set + } +} + /// Returns a hh:mm:ss;frame string of time for SCC format pub fn get_scc_time_str(time: ccx_boundary_time) -> String { // Feel sorry for formatting:( @@ -75,3 +85,159 @@ pub fn get_scc_time_str(time: ccx_boundary_time) -> String { / 1000.0) as u8; format!("{:02}:{:02}:{:02};{:02}", time.hh, time.mm, time.ss, frame) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_get_fts() { + set_tmp_cb_values(); + let timing_ctx = get_temp_timing_ctx(); + + // Case 1 + assert_eq!(timing_ctx.get_fts(1), 727); + // Case 2 + assert_eq!(timing_ctx.get_fts(2), 1061); + // Case 3 + assert_eq!(timing_ctx.get_fts(3), 393); + // Case 4: Unknown field + assert_eq!(timing_ctx.get_fts(43), 0); + } + + #[test] + fn test_get_visible_end() { + set_tmp_cb_values(); + let mut ctx = get_temp_timing_ctx(); + ctx.minimum_fts = 500; + + // Case 1: fts < minimum_fts + assert_eq!(ctx.get_visible_end(3), 393); + assert_eq!(ctx.minimum_fts, 500); // No change in minimum_fts + + // Case 2: fts > minimum_fts + assert_eq!(ctx.get_visible_end(1), 727); + assert_eq!(ctx.minimum_fts, 727); // Change minimum_fts + } + + #[test] + fn test_get_visible_start() { + set_tmp_cb_values(); + let mut ctx = get_temp_timing_ctx(); + ctx.minimum_fts = 500; + + // Case 1: fts <= minimum_fts + assert_eq!(ctx.get_visible_start(3), 501); + + // Case 2: fts <= minimum_fts + assert_eq!(ctx.get_visible_start(1), 727); + } + + #[test] + fn test_get_time_str() { + assert_eq!(get_time_str(0), "00:00:00,000"); + assert_eq!(get_time_str(1000), "00:00:01,000"); + assert_eq!(get_time_str(60000), "00:01:00,000"); + assert_eq!(get_time_str(3600000), "01:00:00,000"); + assert_eq!(get_time_str(86400000), "24:00:00,000"); + } + + #[test] + fn test_ccx_boundary_get_time() { + assert_eq!(ccx_boundary_time::get_time(0), ccx_boundary_time::default()); + assert_eq!( + ccx_boundary_time::get_time(60000), + ccx_boundary_time { + hh: 0, + mm: 1, + ss: 0, + time_in_ms: 60000, + set: Default::default() + } + ); + assert_eq!( + ccx_boundary_time::get_time(3600000), + ccx_boundary_time { + hh: 1, + mm: 0, + ss: 0, + time_in_ms: 3600000, + set: Default::default() + } + ); + assert_eq!( + ccx_boundary_time::get_time(86400000), + ccx_boundary_time { + hh: 24, + mm: 0, + ss: 0, + time_in_ms: 86400000, + set: Default::default() + } + ); + } + + #[test] + fn test_get_scc_time_str() { + assert_eq!( + get_scc_time_str(ccx_boundary_time::default()), + "00:00:00;00" + ); + assert_eq!( + get_scc_time_str(ccx_boundary_time { + hh: 0, + mm: 0, + ss: 1, + time_in_ms: 1000, + set: Default::default() + }), + "00:00:01;00" + ); + assert_eq!( + get_scc_time_str(ccx_boundary_time { + hh: 0, + mm: 1, + ss: 0, + time_in_ms: 60000, + set: Default::default() + }), + "00:01:00;00" + ); + assert_eq!( + get_scc_time_str(ccx_boundary_time { + hh: 1, + mm: 0, + ss: 0, + time_in_ms: 3600000, + set: Default::default() + }), + "01:00:00;00" + ); + assert_eq!( + get_scc_time_str(ccx_boundary_time { + hh: 24, + mm: 0, + ss: 0, + time_in_ms: 86400000, + set: Default::default() + }), + "24:00:00;00" + ); + } + + fn set_tmp_cb_values() { + unsafe { + cb_708 = 10; + cb_field1 = 20; + cb_field2 = 30; + } + } + + fn get_temp_timing_ctx() -> ccx_common_timing_ctx { + ccx_common_timing_ctx { + fts_now: 20, + fts_global: 40, + ..Default::default() + } + } +} diff --git a/src/rust/src/decoder/tv_screen.rs b/src/rust/src/decoder/tv_screen.rs index 055f29739..5a59e3d81 100644 --- a/src/rust/src/decoder/tv_screen.rs +++ b/src/rust/src/decoder/tv_screen.rs @@ -621,3 +621,111 @@ impl dtvcc_tv_screen { } } } + +#[cfg(test)] +mod test { + use crate::utils::get_zero_allocated_obj; + + use super::*; + + #[test] + fn test_clear() { + let mut screen = get_zero_allocated_obj::(); + screen.time_ms_show = 1000; + screen.time_ms_hide = 2000; + screen.chars = [[dtvcc_symbol { sym: 1, init: 2 }; CCX_DTVCC_SCREENGRID_COLUMNS as usize]; + CCX_DTVCC_SCREENGRID_ROWS as usize]; + + // Clear the screen will clear the chars and timings + screen.clear(); + + assert_eq!(screen.time_ms_show, -1); + assert_eq!(screen.time_ms_hide, -1); + for row in 0..CCX_DTVCC_SCREENGRID_ROWS as usize { + for col in 0..CCX_DTVCC_SCREENGRID_COLUMNS as usize { + assert_eq!(screen.chars[row][col], dtvcc_symbol::default()); + } + } + } + + #[test] + fn test_update_time_show() { + let mut screen = get_zero_allocated_obj::(); + screen.time_ms_show = -1; + + // Case 1: time_ms_show = -1 -> Update time show + screen.update_time_show(2000); + assert_eq!(screen.time_ms_show, 2000); + + // Case 2: time_ms_show > time -> Update time show + screen.update_time_show(1000); + assert_eq!(screen.time_ms_show, 1000); + + // Case 3: time_ms_show < time -> Do not update time show + screen.update_time_show(2000); + assert_eq!(screen.time_ms_show, 1000); + } + + #[test] + fn test_update_time_hide() { + let mut screen = get_zero_allocated_obj::(); + screen.time_ms_hide = -1; + + // Case 1: time_ms_show = -1 -> Update time hide + screen.update_time_hide(2000); + assert_eq!(screen.time_ms_hide, 2000); + + // Case 2: time_ms_show < time -> Update time hide + screen.update_time_hide(3000); + assert_eq!(screen.time_ms_hide, 3000); + + // Case 3: time_ms_show > time -> Do not update time hide + screen.update_time_hide(2000); + assert_eq!(screen.time_ms_hide, 3000); + } + + #[test] + fn test_get_write_interval() { + let mut screen = get_zero_allocated_obj::(); + screen.chars[0][2] = dtvcc_symbol::new(0x41); + screen.chars[0][3] = dtvcc_symbol::new(0x42); + screen.chars[0][4] = dtvcc_symbol::new(0x43); + screen.chars[1][4] = dtvcc_symbol::new(0x43); + + // Mulitple row filed + assert_eq!(screen.get_write_interval(0), (2, 4)); + // Single row filed + assert_eq!(screen.get_write_interval(1), (4, 4)); + // Empty rows + assert_eq!(screen.get_write_interval(2), (0, 0)); + } + + #[test] + fn test_count_captions_lines_scc() { + let mut screen = get_zero_allocated_obj::(); + + // No captions + assert_eq!(screen.count_captions_lines_scc(), 0); + + // Set some non-default values + screen.chars[0][2] = dtvcc_symbol::new(0x41); + screen.chars[1][2] = dtvcc_symbol::new(0x42); + screen.chars[2][2] = dtvcc_symbol::new(0x43); + + assert_eq!(screen.count_captions_lines_scc(), 3); + } + + #[test] + fn test_is_row_empty() { + let mut screen = get_zero_allocated_obj::(); + + // Default emty row check + assert!(screen.is_row_empty(0)); + assert!(screen.is_row_empty(1)); + + // Non-default emty row check + screen.chars[0][0] = dtvcc_symbol::new(0x51); + assert!(!screen.is_row_empty(0)); + assert!(screen.is_row_empty(1)); + } +} diff --git a/src/rust/src/decoder/window.rs b/src/rust/src/decoder/window.rs index 868709193..068ea937f 100644 --- a/src/rust/src/decoder/window.rs +++ b/src/rust/src/decoder/window.rs @@ -178,24 +178,9 @@ impl dtvcc_window { } } for col in 0..CCX_DTVCC_MAX_COLUMNS as usize { - // Set pen attributes to default value - self.pen_attribs[row_index][col] = dtvcc_pen_attribs { - pen_size: dtvcc_pen_size::DTVCC_PEN_SIZE_STANDART as i32, - offset: 0, - text_tag: dtvcc_pen_text_tag::DTVCC_PEN_TEXT_TAG_UNDEFINED_12 as i32, - font_tag: 0, - edge_type: dtvcc_pen_edge::DTVCC_PEN_EDGE_NONE as i32, - underline: 0, - italic: 0, - }; - // Set pen color to default value - self.pen_colors[row_index][col] = dtvcc_pen_color { - fg_color: 0x3F, - fg_opacity: 0, - bg_color: 0, - bg_opacity: 0, - edge_color: 0, - }; + // Set pen color and attributes to default value + self.pen_attribs[row_index][col] = dtvcc_pen_attribs::default(); + self.pen_colors[row_index][col] = dtvcc_pen_color::default(); } } } @@ -548,3 +533,224 @@ impl Default for dtvcc_pen_attribs { } } } + +impl PartialEq for dtvcc_pen_attribs { + fn eq(&self, other: &Self) -> bool { + self.pen_size == other.pen_size + && self.offset == other.offset + && self.text_tag == other.text_tag + && self.font_tag == other.font_tag + && self.edge_type == other.edge_type + && self.underline == other.underline + && self.italic == other.italic + } +} + +impl PartialEq for dtvcc_pen_color { + fn eq(&self, other: &Self) -> bool { + self.fg_color == other.fg_color + && self.bg_color == other.bg_color + && self.fg_opacity == other.fg_opacity + && self.edge_color == other.edge_color + && self.bg_opacity == other.bg_opacity + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{cb_708, cb_field1, cb_field2}; + + #[test] + fn test_update_time_show() { + set_test_cb_values(); + let mut timing = get_temp_timing_ctx(); + let mut window = dtvcc_window::default(); + window.update_time_show(&mut timing); + + assert_eq!(window.time_ms_show, 501); + } + + #[test] + fn test_update_time_end() { + set_test_cb_values(); + let mut timing = get_temp_timing_ctx(); + let mut window = dtvcc_window::default(); + window.update_time_hide(&mut timing); + + assert!(window.time_ms_hide == 393 || window.time_ms_hide == 427); + } + + fn create_window(anchor_point: i32, pts: (i32, i32, i32, i32)) -> dtvcc_window { + dtvcc_window { + anchor_point, + anchor_vertical: pts.0, + anchor_horizontal: pts.1, + row_count: pts.2, + col_count: pts.3, + ..Default::default() + } + } + + #[test] + fn test_get_dimensions() { + let pts = (10, 20, 5, 10); + + // Case 1: Top-left anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_LEFT as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (10, 15, 20, 30)); + + // Case 2: Top-center anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_CENTER as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (10, 15, 10, 25)); + + // Case 3: Top-right anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_TOP_RIGHT as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (10, 15, 10, 20)); + + // Case 4: Middle-left anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_LEFT as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (8, 12, 20, 30)); + + // Case 5: Middle-center anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_CENTER as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (8, 12, 15, 25)); + + // Case 6: Middle-right anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_MIDDLE_RIGHT as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (8, 12, 10, 20)); + + // Case 7: Bottom-left anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_LEFT as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (5, 10, 20, 30)); + + // Case 8: Bottom-center anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_CENTER as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (5, 10, 15, 25)); + + // Case 9: Bottom-right anchor point + let window = create_window( + dtvcc_pen_anchor_point::DTVCC_ANCHOR_POINT_BOTTOM_RIGHT as i32, + pts, + ); + assert_eq!(window.get_dimensions().unwrap(), (5, 10, 10, 20)); + } + + #[test] + fn test_clear_row() { + let mut window = dtvcc_window { + memory_reserved: 1, + ..Default::default() + }; + + // Allocate memory for the rows + for row in 0..CCX_DTVCC_MAX_ROWS as usize { + let layout = Layout::array::(CCX_DTVCC_MAX_COLUMNS as usize); + let ptr = unsafe { alloc_zeroed(layout.unwrap()) }; + window.rows[row] = ptr as *mut dtvcc_symbol; + } + + window.pen_attribs[2][3] = dtvcc_pen_attribs { + pen_size: dtvcc_pen_size::DTVCC_PEN_SIZE_SMALL as i32, + offset: 10, + ..Default::default() + }; + window.pen_colors[2][3] = dtvcc_pen_color { + fg_color: 0x12, + fg_opacity: 0x34, + ..Default::default() + }; + + window.clear_row(2); + + // Verify the row has been cleared + for col in 0..CCX_DTVCC_MAX_COLUMNS as usize { + assert_eq!(window.pen_attribs[2][col], dtvcc_pen_attribs::default()); + assert_eq!(window.pen_colors[2][col], dtvcc_pen_color::default()); + } + } + + #[test] + fn test_rollup() { + let mut window = dtvcc_window::default(); + + // Allocate memory for the rows + for row in 0..CCX_DTVCC_MAX_ROWS as usize { + let layout = Layout::array::(CCX_DTVCC_MAX_COLUMNS as usize); + let ptr = unsafe { alloc_zeroed(layout.unwrap()) }; + window.rows[row] = ptr as *mut dtvcc_symbol; + } + + window.pen_attribs[0][0] = dtvcc_pen_attribs { + pen_size: dtvcc_pen_size::DTVCC_PEN_SIZE_SMALL as i32, + offset: 10, + ..Default::default() + }; + window.pen_colors[0][0] = dtvcc_pen_color { + fg_color: 0x12, + fg_opacity: 0x34, + ..Default::default() + }; + + window.pen_attribs[1][1] = dtvcc_pen_attribs { + pen_size: dtvcc_pen_size::DTVCC_PEN_SIZE_LARGE as i32, + offset: 20, + ..Default::default() + }; + window.pen_colors[1][1] = dtvcc_pen_color { + fg_color: 0x56, + fg_opacity: 0x78, + ..Default::default() + }; + window.row_count = 2; + + window.rollup(); + + // Verify the rows have been rolled up + for col in 0..CCX_DTVCC_MAX_COLUMNS as usize { + assert_eq!(window.pen_attribs[0][col], window.pen_attribs[1][col]); + assert_eq!(window.pen_colors[0][col], window.pen_colors[1][col]); + } + } + + fn set_test_cb_values() { + unsafe { + cb_708 = 10; + cb_field1 = 20; + cb_field2 = 30; + } + } + + fn get_temp_timing_ctx() -> ccx_common_timing_ctx { + ccx_common_timing_ctx { + fts_now: 20, + fts_global: 40, + minimum_fts: 500, + ..Default::default() + } + } +} diff --git a/src/rust/src/hardsubx/imgops.rs b/src/rust/src/hardsubx/imgops.rs index 293f75665..a783d1d24 100644 --- a/src/rust/src/hardsubx/imgops.rs +++ b/src/rust/src/hardsubx/imgops.rs @@ -22,3 +22,66 @@ pub extern "C" fn rgb_to_lab(R: f32, G: f32, B: f32, L: &mut f32, a: &mut f32, b *a = lab_rep.a; *b = lab_rep.b; } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_rgb_to_hsv() { + let (mut h, mut s, mut v) = (0.0, 0.0, 0.0); + + // Red (255, 0, 0) + rgb_to_hsv(255_f32, 0.0, 0.0, &mut h, &mut s, &mut v); + assert_eq!(h, 0.0); + assert_eq!(s, 1.0); + assert_eq!(v, 1.0); + + // Green (0, 255, 0) + rgb_to_hsv(0.0, 255_f32, 0.0, &mut h, &mut s, &mut v); + assert_eq!(h, 120.0); + assert_eq!(s, 1.0); + assert_eq!(v, 1.0); + + // Blue (0, 0, 255) + rgb_to_hsv(0.0, 0.0, 255_f32, &mut h, &mut s, &mut v); + assert_eq!(h, 240.0); + assert_eq!(s, 1.0); + assert_eq!(v, 1.0); + + // White (255, 255, 255) + rgb_to_hsv(255_f32, 255_f32, 255_f32, &mut h, &mut s, &mut v); + assert_eq!(h, 0.0); + assert_eq!(s, 0.0); + assert_eq!(v, 1.0); + } + + #[test] + fn test_rgb_to_lab() { + let (mut l, mut a, mut b) = (0.0, 0.0, 0.0); + + // Red (255, 0, 0) + rgb_to_lab(1.0, 0.0, 0.0, &mut l, &mut a, &mut b); + assert_eq!(l.floor(), 53.0); + assert_eq!(a.floor(), 80.0); + assert_eq!(b.floor(), 67.0); + + // Green (0, 255, 0) + rgb_to_lab(0.0, 1.0, 0.0, &mut l, &mut a, &mut b); + assert_eq!(l.floor(), 87.0); + assert_eq!(a.floor(), -87.0); + assert_eq!(b.floor(), 83.0); + + // Blue (0, 0, 255) + rgb_to_lab(0.0, 0.0, 1.0, &mut l, &mut a, &mut b); + assert_eq!(l.floor(), 32.0); + assert_eq!(a.floor(), 79.0); + assert_eq!(b.floor(), -108.0); + + // White (255, 255, 255) + rgb_to_lab(1.0, 1.0, 1.0, &mut l, &mut a, &mut b); + assert_eq!(l.floor(), 100.0); + assert_eq!(a.floor(), 0.0); + assert_eq!(b.floor(), 0.0); + } +} diff --git a/src/rust/src/hardsubx/utility.rs b/src/rust/src/hardsubx/utility.rs index c131a6a59..5a978223f 100644 --- a/src/rust/src/hardsubx/utility.rs +++ b/src/rust/src/hardsubx/utility.rs @@ -88,3 +88,55 @@ pub unsafe extern "C" fn edit_distance( &mut dp_array, ) as c_int } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_convert_pts_to_ns() { + let time_base = AVRational { num: 1, den: 100 }; + let pts = 42; + let expected_ns = 420_000; + let result = convert_pts_to_ns(pts, time_base); + assert_eq!(result, expected_ns); + } + + #[test] + fn test_convert_pts_to_ms() { + let time_base = AVRational { num: 1, den: 100 }; + let pts = 42; + let expected_ms = 420; + let result = convert_pts_to_ms(pts, time_base); + assert_eq!(result, expected_ms); + } + + #[test] + fn test_convert_pts_to_s() { + let time_base = AVRational { num: 1, den: 100 }; + let pts = 42; + let expected_s = 0; + let result = convert_pts_to_s(pts, time_base); + assert_eq!(result, expected_s); + } + + #[test] + fn test_edit_distance() { + unsafe { + let word1 = ffi::CString::new("kitten").unwrap().into_raw(); + let len1 = 6; + + let word2 = ffi::CString::new("sitting").unwrap().into_raw(); + let len2 = 7; + + let distance = edit_distance(word1, word2, len1, len2); + + // Edit distance between "kitten" and "sitting" is 3 + assert_eq!(distance, 3); + + // Safety: Deallocate C strings to avoid memory leaks + let _ = ffi::CString::from_raw(word1); + let _ = ffi::CString::from_raw(word2); + } + } +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 981b59790..7c2829d26 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -40,10 +40,21 @@ use std::ffi::CStr; use crate::common::ExitCode; +#[cfg(test)] +static mut cb_708: c_int = 0; +#[cfg(test)] +static mut cb_field1: c_int = 0; +#[cfg(test)] +static mut cb_field2: c_int = 0; + +#[cfg(not(test))] extern "C" { static mut cb_708: c_int; static mut cb_field1: c_int; static mut cb_field2: c_int; +} + +extern "C" { static mut MPEG_CLOCK_FREQ: c_int; static mut tlt_config: ccx_s_teletext_config; } @@ -252,3 +263,53 @@ pub unsafe extern "C" fn ccxr_parse_parameters( 0 } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_verify_parity() { + // Odd parity + assert!(verify_parity(0b1010001)); + + // Even parity + assert!(!verify_parity(0b1000001)); + } + + #[test] + fn test_validate_cc_pair() { + // Valid CEA-708 data + let mut cc_block = [0x97, 0x1F, 0x3C]; + assert!(validate_cc_pair(&mut cc_block)); + + // Invalid CEA-708 data + let mut cc_block = [0x93, 0x1F, 0x3C]; + assert!(!validate_cc_pair(&mut cc_block)); + + // Valid CEA-608 data + let mut cc_block = [0x15, 0x2F, 0x7D]; + assert!(validate_cc_pair(&mut cc_block)); + // Check for replaced bit when 1st byte doesn't pass parity + assert_eq!(cc_block[1], 0x7F); + + // Invalid CEA-608 data + let mut cc_block = [0x15, 0x2F, 0x5E]; + assert!(!validate_cc_pair(&mut cc_block)); + } + + #[test] + fn test_do_cb() { + let mut dtvcc_ctx = utils::get_zero_allocated_obj::(); + let mut dtvcc = Dtvcc::new(&mut dtvcc_ctx); + + let mut decoder_ctx = lib_cc_decode::default(); + let cc_block = [0x97, 0x1F, 0x3C]; + + assert!(do_cb(&mut decoder_ctx, &mut dtvcc, &cc_block)); + assert_eq!(decoder_ctx.current_field, 3); + assert_eq!(decoder_ctx.cc_stats[3], 1); + assert_eq!(decoder_ctx.processed_enough, 0); + assert_eq!(unsafe { cb_708 }, 11); + } +} diff --git a/src/rust/src/utils.rs b/src/rust/src/utils.rs index 39067251d..a9ba389b7 100644 --- a/src/rust/src/utils.rs +++ b/src/rust/src/utils.rs @@ -55,3 +55,26 @@ pub fn string_to_c_chars(strs: Vec) -> *mut *mut c_char { ptr as *mut *mut c_char } + +/// This function creates a new object of type `T` and fills it with zeros. +/// +/// This function uses the `std::alloc::alloc_zeroed` function to allocate +/// memory for new object of type `T` +/// The allocated memory is then wrapped in a `Box` and returned. +/// +/// # Safety +/// This function is unsafe because it directly interacts with low-level +/// memory allocation and deallocation functions. Misusing this function +/// can lead to memory leaks, undefined behavior, and other memory-related +/// issues. It is the caller's responsibility to ensure that the returned +/// `Box` is used and dropped correctly. +pub fn get_zero_allocated_obj() -> Box { + use std::alloc::{alloc_zeroed, Layout}; + + unsafe { + let layout = Layout::new::(); + let allocation = alloc_zeroed(layout) as *mut T; + + Box::from_raw(allocation) + } +}