LCOV - code coverage report
Current view: top level - libs/postgres_ffi/wal_craft/src - xlog_utils_test.rs (source / functions) Coverage Total Hit
Test: e402c46de0a007db6b48dddbde450ddbb92e6ceb.info Lines: 98.7 % 159 157
Test Date: 2024-06-25 10:31:23 Functions: 100.0 % 60 60

            Line data    Source code
       1              : //! Tests for postgres_ffi xlog_utils module. Put it here to break cyclic dependency.
       2              : 
       3              : use super::*;
       4              : use crate::{error, info};
       5              : use regex::Regex;
       6              : use std::cmp::min;
       7              : use std::fs::{self, File};
       8              : use std::io::Write;
       9              : use std::{env, str::FromStr};
      10              : use utils::const_assert;
      11              : use utils::lsn::Lsn;
      12              : 
      13           18 : fn init_logging() {
      14           18 :     let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(format!(
      15           18 :         "crate=info,postgres_ffi::{PG_MAJORVERSION}::xlog_utils=trace"
      16           18 :     )))
      17           18 :     .is_test(true)
      18           18 :     .try_init();
      19           18 : }
      20              : 
      21              : /// Test that find_end_of_wal returns the same results as pg_dump on various
      22              : /// WALs created by Crafter.
      23           18 : fn test_end_of_wal<C: crate::Crafter>(test_name: &str) {
      24           18 :     use crate::*;
      25           18 : 
      26           18 :     let pg_version = PG_MAJORVERSION[1..3].parse::<u32>().unwrap();
      27           18 : 
      28           18 :     // Craft some WAL
      29           18 :     let top_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
      30           18 :         .join("..")
      31           18 :         .join("..")
      32           18 :         .join("..");
      33           18 :     let cfg = Conf {
      34           18 :         pg_version,
      35           18 :         pg_distrib_dir: top_path.join("pg_install"),
      36           18 :         datadir: top_path.join(format!("test_output/{}-{PG_MAJORVERSION}", test_name)),
      37           18 :     };
      38           18 :     if cfg.datadir.exists() {
      39            9 :         fs::remove_dir_all(&cfg.datadir).unwrap();
      40            9 :     }
      41           18 :     cfg.initdb().unwrap();
      42           18 :     let srv = cfg.start_server().unwrap();
      43           18 :     let intermediate_lsns = C::craft(&mut srv.connect_with_timeout().unwrap()).unwrap();
      44           18 :     let intermediate_lsns: Vec<Lsn> = intermediate_lsns
      45           18 :         .iter()
      46           30 :         .map(|&lsn| u64::from(lsn).into())
      47           18 :         .collect();
      48           18 :     // Kill postgres. Note that it might have inserted to WAL something after
      49           18 :     // 'craft' did its job.
      50           18 :     srv.kill();
      51           18 : 
      52           18 :     // Check find_end_of_wal on the initial WAL
      53           18 :     let last_segment = cfg
      54           18 :         .wal_dir()
      55           18 :         .read_dir()
      56           18 :         .unwrap()
      57           48 :         .map(|f| f.unwrap().file_name().into_string().unwrap())
      58           48 :         .filter(|fname| IsXLogFileName(fname))
      59           18 :         .max()
      60           18 :         .unwrap();
      61           18 :     let expected_end_of_wal = find_pg_waldump_end_of_wal(&cfg, &last_segment);
      62           48 :     for start_lsn in intermediate_lsns
      63           18 :         .iter()
      64           18 :         .chain(std::iter::once(&expected_end_of_wal))
      65              :     {
      66              :         // Erase all WAL before `start_lsn` to ensure it's not used by `find_end_of_wal`.
      67              :         // We assume that `start_lsn` is non-decreasing.
      68           48 :         info!(
      69           48 :             "Checking with start_lsn={}, erasing WAL before it",
      70              :             start_lsn
      71              :         );
      72          132 :         for file in fs::read_dir(cfg.wal_dir()).unwrap().flatten() {
      73          132 :             let fname = file.file_name().into_string().unwrap();
      74          132 :             if !IsXLogFileName(&fname) {
      75           48 :                 continue;
      76           84 :             }
      77           84 :             let (segno, _) = XLogFromFileName(&fname, WAL_SEGMENT_SIZE);
      78           84 :             let seg_start_lsn = XLogSegNoOffsetToRecPtr(segno, 0, WAL_SEGMENT_SIZE);
      79           84 :             if seg_start_lsn > u64::from(*start_lsn) {
      80           12 :                 continue;
      81           72 :             }
      82           72 :             let mut f = File::options().write(true).open(file.path()).unwrap();
      83           72 :             const ZEROS: [u8; WAL_SEGMENT_SIZE] = [0u8; WAL_SEGMENT_SIZE];
      84           72 :             f.write_all(
      85           72 :                 &ZEROS[0..min(
      86           72 :                     WAL_SEGMENT_SIZE,
      87           72 :                     (u64::from(*start_lsn) - seg_start_lsn) as usize,
      88           72 :                 )],
      89           72 :             )
      90           72 :             .unwrap();
      91              :         }
      92           48 :         check_end_of_wal(&cfg, &last_segment, *start_lsn, expected_end_of_wal);
      93              :     }
      94           18 : }
      95              : 
      96           18 : fn find_pg_waldump_end_of_wal(cfg: &crate::Conf, last_segment: &str) -> Lsn {
      97           18 :     // Get the actual end of WAL by pg_waldump
      98           18 :     let waldump_output = cfg
      99           18 :         .pg_waldump("000000010000000000000001", last_segment)
     100           18 :         .unwrap()
     101           18 :         .stderr;
     102           18 :     let waldump_output = std::str::from_utf8(&waldump_output).unwrap();
     103           18 :     let caps = match Regex::new(r"invalid record length at (.+):")
     104           18 :         .unwrap()
     105           18 :         .captures(waldump_output)
     106              :     {
     107           18 :         Some(caps) => caps,
     108              :         None => {
     109            0 :             error!("Unable to parse pg_waldump's stderr:\n{}", waldump_output);
     110            0 :             panic!();
     111              :         }
     112              :     };
     113           18 :     let waldump_wal_end = Lsn::from_str(caps.get(1).unwrap().as_str()).unwrap();
     114           18 :     info!("waldump erred on {}", waldump_wal_end);
     115           18 :     waldump_wal_end
     116           18 : }
     117              : 
     118           48 : fn check_end_of_wal(
     119           48 :     cfg: &crate::Conf,
     120           48 :     last_segment: &str,
     121           48 :     start_lsn: Lsn,
     122           48 :     expected_end_of_wal: Lsn,
     123           48 : ) {
     124           48 :     // Check end_of_wal on non-partial WAL segment (we treat it as fully populated)
     125           48 :     // let wal_end = find_end_of_wal(&cfg.wal_dir(), WAL_SEGMENT_SIZE, start_lsn).unwrap();
     126           48 :     // info!(
     127           48 :     //     "find_end_of_wal returned wal_end={} with non-partial WAL segment",
     128           48 :     //     wal_end
     129           48 :     // );
     130           48 :     // assert_eq!(wal_end, expected_end_of_wal_non_partial);
     131           48 : 
     132           48 :     // Rename file to partial to actually find last valid lsn, then rename it back.
     133           48 :     fs::rename(
     134           48 :         cfg.wal_dir().join(last_segment),
     135           48 :         cfg.wal_dir().join(format!("{}.partial", last_segment)),
     136           48 :     )
     137           48 :     .unwrap();
     138           48 :     let wal_end = find_end_of_wal(&cfg.wal_dir(), WAL_SEGMENT_SIZE, start_lsn).unwrap();
     139           48 :     info!(
     140           48 :         "find_end_of_wal returned wal_end={} with partial WAL segment",
     141              :         wal_end
     142              :     );
     143           48 :     assert_eq!(wal_end, expected_end_of_wal);
     144           48 :     fs::rename(
     145           48 :         cfg.wal_dir().join(format!("{}.partial", last_segment)),
     146           48 :         cfg.wal_dir().join(last_segment),
     147           48 :     )
     148           48 :     .unwrap();
     149           48 : }
     150              : 
     151              : const_assert!(WAL_SEGMENT_SIZE == 16 * 1024 * 1024);
     152              : 
     153              : #[test]
     154            6 : pub fn test_find_end_of_wal_simple() {
     155            6 :     init_logging();
     156            6 :     test_end_of_wal::<crate::Simple>("test_find_end_of_wal_simple");
     157            6 : }
     158              : 
     159              : #[test]
     160            6 : pub fn test_find_end_of_wal_crossing_segment_followed_by_small_one() {
     161            6 :     init_logging();
     162            6 :     test_end_of_wal::<crate::WalRecordCrossingSegmentFollowedBySmallOne>(
     163            6 :         "test_find_end_of_wal_crossing_segment_followed_by_small_one",
     164            6 :     );
     165            6 : }
     166              : 
     167              : #[test]
     168            6 : pub fn test_find_end_of_wal_last_crossing_segment() {
     169            6 :     init_logging();
     170            6 :     test_end_of_wal::<crate::LastWalRecordCrossingSegment>(
     171            6 :         "test_find_end_of_wal_last_crossing_segment",
     172            6 :     );
     173            6 : }
     174              : 
     175              : /// Check the math in update_next_xid
     176              : ///
     177              : /// NOTE: These checks are sensitive to the value of XID_CHECKPOINT_INTERVAL,
     178              : /// currently 1024.
     179              : #[test]
     180            6 : pub fn test_update_next_xid() {
     181            6 :     let checkpoint_buf = [0u8; std::mem::size_of::<CheckPoint>()];
     182            6 :     let mut checkpoint = CheckPoint::decode(&checkpoint_buf).unwrap();
     183            6 : 
     184            6 :     checkpoint.nextXid = FullTransactionId { value: 10 };
     185            6 :     assert_eq!(checkpoint.nextXid.value, 10);
     186              : 
     187              :     // The input XID gets rounded up to the next XID_CHECKPOINT_INTERVAL
     188              :     // boundary
     189            6 :     checkpoint.update_next_xid(100);
     190            6 :     assert_eq!(checkpoint.nextXid.value, 1024);
     191              : 
     192              :     // No change
     193            6 :     checkpoint.update_next_xid(500);
     194            6 :     assert_eq!(checkpoint.nextXid.value, 1024);
     195            6 :     checkpoint.update_next_xid(1023);
     196            6 :     assert_eq!(checkpoint.nextXid.value, 1024);
     197              : 
     198              :     // The function returns the *next* XID, given the highest XID seen so
     199              :     // far. So when we pass 1024, the nextXid gets bumped up to the next
     200              :     // XID_CHECKPOINT_INTERVAL boundary.
     201            6 :     checkpoint.update_next_xid(1024);
     202            6 :     assert_eq!(checkpoint.nextXid.value, 2048);
     203            6 : }
     204              : 
     205              : #[test]
     206            6 : pub fn test_encode_logical_message() {
     207            6 :     let expected = [
     208            6 :         64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 170, 34, 166, 227, 255, 38,
     209            6 :         0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 112, 114, 101, 102,
     210            6 :         105, 120, 0, 109, 101, 115, 115, 97, 103, 101,
     211            6 :     ];
     212            6 :     let actual = encode_logical_message("prefix", "message");
     213            6 :     assert_eq!(expected, actual[..]);
     214            6 : }
        

Generated by: LCOV version 2.1-beta