From 5b178d23f10f7f9612f60a6cfa0e53daf759049d Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Mon, 9 Feb 2026 04:01:10 +0000 Subject: [PATCH 01/17] NPI-4460 misc supporting changes: update read_clk() to support byte input for easier testing, add stringify_warnings() utility for easier output of warning list from unit tests when warning count is unexpected, formatting fixes. --- gnssanalysis/gn_io/clk.py | 9 +++++---- gnssanalysis/gn_utils.py | 22 ++++++++++++++++++++++ tests/test_sp3.py | 7 ++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/gnssanalysis/gn_io/clk.py b/gnssanalysis/gn_io/clk.py index 9d3f789..72f53c0 100644 --- a/gnssanalysis/gn_io/clk.py +++ b/gnssanalysis/gn_io/clk.py @@ -1,6 +1,7 @@ """RINEX CLK file parsing function""" import logging as _logging +from pathlib import Path import re as _re from io import BytesIO as _BytesIO from typing import Union as _Union @@ -15,8 +16,8 @@ _RE_LINE = _re.compile(rb"(AS[ ]G.+)") # GPS SV line (other GNSS may not have STD) -def read_clk(clk_path): - content = _gn_io.common.path2bytes(str(clk_path)) +def read_clk(clk_path_or_bytes: _Union[Path, str, bytes]) -> _pd.DataFrame: + content = _gn_io.common.path2bytes(clk_path_or_bytes) data_b = content.find(b"END OF HEADER") + 13 data_b += content[data_b : data_b + 20].find(b"\n") + 1 @@ -77,12 +78,12 @@ def get_sv_clocks(clk_df: _pd.DataFrame) -> _pd.Series: :raises IndexError: Raise error if the dataframe is not indexed correctly :return _pd.Series: Retrieved satellite clocks """ - if clk_df.index.names == ['A', 'J2000', 'CODE']: + if clk_df.index.names == ["A", "J2000", "CODE"]: # fastest method to grab a specific category!, same as clk_df.EST.loc['AS'] but >6 times faster AS_cat_code = clk_df.index.levels[0].categories.get_loc("AS") mask = clk_df.index.codes[0] == AS_cat_code return _pd.Series(data=clk_df.values[:, 0][mask], index=clk_df.index.droplevel(0)[mask]) - elif clk_df.index.names == ['J2000', 'PRN']: + elif clk_df.index.names == ["J2000", "PRN"]: return _pd.Series(data=clk_df[("EST", "CLK")].values, index=clk_df.index) else: raise IndexError("Incorrect index names of dataframe") diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 8abb9b1..9c805f8 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -997,6 +997,28 @@ def __exit__(self, type, value, traceback): print(self.readout) +def stringify_warnings(captured_warnings: list[warnings.WarningMessage]) -> str: + """ + Convenience function to convert a list of warning messages to a string. + E.g. output: + Warning message #1: Some warning + Warning message #2: Some other warning + ... + + :param captured_warnings: list of warning message objects (e.g. from UnitTest's _AssertWarnsContext.warnings) + :type captured_warnings: list[warnings.WarningMessage] + :return: rendered string for multi-line log output + :rtype: str + """ + aggregate_message = "" + for i in range(len(captured_warnings)): + w = captured_warnings[i] + aggregate_message += f"Warning message #{i+1}: {str(w.message)}\n" + return aggregate_message + # Alternatively: + # return f"{''.join('MESSAGE -> ' + str(w.message) + NEWLINE for w in captured_warnings)}" + + def sha256(bytes_to_hash: bytes) -> str: """ Convenience wrapper to quickly call hashlib.sha256 and return a hex digest string diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 15cd553..5d6f39f 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -8,7 +8,7 @@ from gnssanalysis.filenames import convert_nominal_span, determine_properties_from_filename import gnssanalysis.gn_io.sp3 as sp3 -from gnssanalysis.gn_utils import STRICT_OFF, STRICT_RAISE, STRICT_WARN, trim_line_ends +from gnssanalysis.gn_utils import STRICT_OFF, STRICT_RAISE, STRICT_WARN, stringify_warnings, trim_line_ends from test_datasets.sp3_test_data import ( fake_header_version_a, fake_header_version_b, @@ -496,8 +496,9 @@ def test_clean_sp3_orb(self): ) self.assertEqual( len(captured_warnings), - 1, - "Only expected one warning, about failing to get path. Check what other warnings are being raised!", + 1, # Second warning is about pandas 3 deprecations. + "Only expected one warning, about failing to get path. " + f"Check all warnings below:\n{stringify_warnings(captured_warnings)}", ) def test_gen_sp3_fundamentals(self): From 85f0d340f1b41a5a576dc0b5a8eaf27f7eff2ad6 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Mon, 9 Feb 2026 04:04:17 +0000 Subject: [PATCH 02/17] NPI-4460 implement baselining in test_clk --- gnssanalysis/gn_io/clk.py | 2 +- tests/test_clk.py | 93 ++++++++++++++---- .../TestClk/test_clk_read.pickledlist | Bin 0 -> 4973 bytes .../TestClk/test_clk_read.pickledlist_sha256 | 1 + .../TestClk/test_diff_clk.pickledlist | Bin 0 -> 18592 bytes .../TestClk/test_diff_clk.pickledlist_sha256 | 1 + 6 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 tests/unittest_baselines/TestClk/test_clk_read.pickledlist create mode 100644 tests/unittest_baselines/TestClk/test_clk_read.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestClk/test_diff_clk.pickledlist create mode 100644 tests/unittest_baselines/TestClk/test_diff_clk.pickledlist_sha256 diff --git a/gnssanalysis/gn_io/clk.py b/gnssanalysis/gn_io/clk.py index 72f53c0..e625a81 100644 --- a/gnssanalysis/gn_io/clk.py +++ b/gnssanalysis/gn_io/clk.py @@ -33,7 +33,7 @@ def read_clk(clk_path_or_bytes: _Union[Path, str, bytes]) -> _pd.DataFrame: clk_cols += [10] clk_names += ["STD"] - clk_df = _pd.read_csv( + clk_df = _pd.read_csv( # TODO consider updating to read_fwf() _BytesIO(data), sep="\\s+", # delim_whitespace is deprecated header=None, diff --git a/tests/test_clk.py b/tests/test_clk.py index 5e1fb05..12ee148 100644 --- a/tests/test_clk.py +++ b/tests/test_clk.py @@ -1,7 +1,9 @@ -from pyfakefs.fake_filesystem_unittest import TestCase +from pandas import DataFrame +from unittest import TestCase import gnssanalysis.gn_io.clk as clk import gnssanalysis.gn_diffaux as gn_diffaux +from gnssanalysis.gn_utils import UnitTestBaseliner from test_datasets.clk_test_data import ( # first dataset is a truncated version of file IGS0OPSRAP_20240400000_01D_05M_CLK.CLK: @@ -12,18 +14,13 @@ class TestClk(TestCase): - def setUp(self): - self.setUpPyfakefs() - self.fs.reset() def test_clk_read(self): - self.fs.reset() - file_paths = ["/fake/dir/file0.clk", "/fake/dir/file1.clk"] - self.fs.create_file(file_paths[0], contents=input_data_igs) - self.fs.create_file(file_paths[1], contents=input_data_gfz) + clk_df_igs: DataFrame = clk.read_clk(clk_path_or_bytes=input_data_igs) + clk_df_gfz: DataFrame = clk.read_clk(clk_path_or_bytes=input_data_gfz) - clk_df_igs = clk.read_clk(clk_path=file_paths[0]) - clk_df_gfz = clk.read_clk(clk_path=file_paths[1]) + # To help detect changes / regressions, check the dataframe we constructed against the stored hash. + # If they differ, load stored DF from pickle and print the difference. self.assertEqual(len(clk_df_igs), 93, msg="Check that data generally read into df as expected") self.assertEqual(len(clk_df_gfz), 90, msg="Check that data generally read into df as expected") @@ -34,17 +31,24 @@ def test_clk_read(self): self.assertEqual(clk_df_igs["EST"].iloc[-1], -0.0006105557076344, msg="Check last datapoint is correct") self.assertEqual(clk_df_gfz["EST"].iloc[-1], -0.000610553573006, msg="Check last datapoint is correct") + # Baseline (manually) to disk + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.record_baseline([clk_df_igs, clk_df_gfz]) + + # Verify against on disk baseline + self.assertTrue(UnitTestBaseliner.verify([clk_df_igs, clk_df_gfz]), "Hash verify should succeed") + def test_diff_clk(self): """ Note this also tests the now deprecated version, compare_clk() """ - self.fs.reset() # Reset pyfakefs to delete any files which may have persisted from a previous test - file_paths = ["/fake/dir/file0.clk", "/fake/dir/file1.clk"] - self.fs.create_file(file_paths[0], contents=input_data_igs) - self.fs.create_file(file_paths[1], contents=input_data_gfz) - clk_df_igs = clk.read_clk(clk_path=file_paths[0]) - clk_df_gfz = clk.read_clk(clk_path=file_paths[1]) + # List of dataframes created during this test, to compare against baselined results on disk (regression check). + dfs_to_verify: list[object] = [] + + # Don't include these in the baseline, as test_clk_read() already looks after that. + clk_df_igs = clk.read_clk(clk_path_or_bytes=input_data_igs) + clk_df_gfz = clk.read_clk(clk_path_or_bytes=input_data_gfz) # Deprecated version # Ensure depreciation warnings are raised, but don't print them. @@ -60,6 +64,7 @@ def test_diff_clk(self): result_epoch_G07 = gn_diffaux.compare_clk(clk_a=clk_df_igs, clk_b=clk_df_gfz, norm_types=["epoch", "G07"]) result_daily_G08 = gn_diffaux.compare_clk(clk_a=clk_df_igs, clk_b=clk_df_gfz, norm_types=["daily", "G08"]) result_G09_G11 = gn_diffaux.compare_clk(clk_a=clk_df_igs, clk_b=clk_df_gfz, norm_types=["G09", "G11"]) + captured_warnings = warning_assessor.warnings self.assertEqual( "compare_clk() is deprecated. Please use diff_clk() and note that the clk inputs are in opposite order", @@ -79,12 +84,29 @@ def test_diff_clk(self): self.assertEqual(result_epoch_only["G04"].iloc[0], 2.7128617820053325e-12, msg="Check datapoint is correct") self.assertEqual(result_sv_only["G05"].iloc[0], 1.1623200004470119e-10, msg="Check datapoint is correct") self.assertEqual(result_G06["G06"].iloc[0], 0.0, msg="Check datapoint is correct") - self.assertEqual(result_daily_epoch_G04["G07"].iloc[0], 1.3071733365871419e-11, msg="Check datapoint is correct") + self.assertEqual( + result_daily_epoch_G04["G07"].iloc[0], 1.3071733365871419e-11, msg="Check datapoint is correct" + ) self.assertEqual(result_epoch_G07["G08"].iloc[0], -3.3217389966032004e-11, msg="Check datapoint is correct") self.assertEqual(result_daily_G08["G09"].iloc[-1], 1.3818666534399365e-12, msg="Check datapoint is correct") self.assertEqual(result_G09_G11["G11"].iloc[-1], 0.0, msg="Check datapoint is correct") self.assertEqual(result_G09_G11["G01"].iloc[-1], 8.94520000606358e-11, msg="Check datapoint is correct") + # Add all these output DFs to the list to be compared against the baseline on disk + dfs_to_verify.extend( + [ + result_default, + result_daily_only, + result_epoch_only, + result_sv_only, + result_G06, + result_daily_epoch_G04, + result_epoch_G07, + result_daily_G08, + result_G09_G11, + ] + ) + # New version (clk order flipped) result_default = gn_diffaux.diff_clk(clk_baseline=clk_df_gfz, clk_test=clk_df_igs) result_daily_only = gn_diffaux.diff_clk(clk_baseline=clk_df_gfz, clk_test=clk_df_igs, norm_types=["daily"]) @@ -117,3 +139,40 @@ def test_diff_clk(self): self.assertEqual(result_daily_G08["G09"].iloc[-1], 1.3818666534399365e-12, msg="Check datapoint is correct") self.assertEqual(result_G09_G11["G11"].iloc[-1], 0.0, msg="Check datapoint is correct") self.assertEqual(result_G09_G11["G01"].iloc[-1], 8.94520000606358e-11, msg="Check datapoint is correct") + + dfs_to_verify.extend( + [ + result_default, + result_daily_only, + result_epoch_only, + result_sv_only, + result_G06, + result_daily_epoch_G04, + result_epoch_G07, + result_daily_G08, + result_G09_G11, + ] + ) + + # Baseline establishment (manual use only). DO NOT commit this enabled: + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.record_baseline(dfs_to_verify) + + # Verify all dataframes against recorded baseline on disk + self.assertTrue( + UnitTestBaseliner.verify(dfs_to_verify), "Validation should succeed (unless in baselining mode)" + ) + + +# if __name__ == "__main__": +# # For debugger use + +# logging.basicConfig(format="%(levelname)s: %(message)s") +# logger = logging.getLogger() +# logger.setLevel(logging.DEBUG) + +# os.chdir("./tests") + +# test_clk = TestClk() +# test_clk.test_diff_clk() +# test_clk.test_clk_read() diff --git a/tests/unittest_baselines/TestClk/test_clk_read.pickledlist b/tests/unittest_baselines/TestClk/test_clk_read.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..3cd94bb87c9787bd2995a3926eb2c6f23cdbb026 GIT binary patch literal 4973 zcmd6q4LDT!9>>iv-YJ#0uBBI{ux79n|3(Z#(T0?=(_lERF*GyETZ$5D6zfz9)$WZ| z-i_Ci$%<^N7wfKGb}QMMic~7MZne3;b4I(hz1`>Tv(IzS^Z1`L&iS7I|MP$Qe$OVQ zUBMF-$Rh}!E!K(Q@IyI5%aCXR*D_4NiQ?j7H5Nz2aU-vDakCVB13ah1jtUpxV&m5z zkKl{A0zQW)w2b2LIpJJ^5EpAY^P)o{m&kwPxwu$wOu^{xzGL`Wo$ z%MK$;G`C z(r_0_FrJPt!)JR@yr^D5TXAf>m=-FEkKqy`YM4EqnIXTgD5f~#-rnAxqi~Rgmx2fn z&U8LcQ&2E5bln|3#gwU>zH+-#!z||PWha_Rvud)eWs@G$7UyPNq)n9GI8rdJ{u^iM{AY8A&ssC3)%}5WsWB|( z-fGWq_qaKGY`sKq?Ov7<wn!)}X=2_b zi%8&}=$z5*0^B<;%qsl~xTCB4vk7i`uPxOCxIHYs+&0%hTUzDx%zZa-`#PP^9R=q#Rgip8Nh9^ZcBk3aH}m`XS5!;zwDgnoeA87T1|NmfP0yn z#wiEjZf%eEp99=qdbDb*eAU*H`M|9G5O4?dbc`MVZlA$}S89M;W7Sd=1>AM<|K`>M zx0S5NW}kKg`cbVZwm_p4NwN)#9mZFoyd;6ChRZ=@IvRI<#giITd!QOWXxxA{~x=Hv9%Pnzto~&8k9kPTW6ZKiGscZ|^X7Ehs}@bod7e_m-iQ zh<{P=>ME3O66bWIxE#%B&$>WqJcL$hIT^eZ*P@q&7q+Se7Nh2@aR#lP62vx-obA?J zjjV@i?g3NrbcW$^`gI-*FY+Q4u7M*lwJsLh(js|=@_9bFXsBA=B zoAII!`45CX%N$>W@>&bm+&^27ZYgVR>zP)K)LyhGZB4I1eXE!5N0dgiw=1mA^KluP zAzR&{aj6n{?be^p`+iRjitj*f4< zXmZ$1g8JfG+v8T%p_i&_%;UI+Q9~}abB{$m;tsoveqY{zit27fgvA~~6aIP3^&VOCwLcl7?#0(7nmxa1{wUCj03wMY#gCX`COrGf;wz^=r;WwiY3qd!g2m z)LPW%c{wkqL4sZX86rEf~;dO@{LiNTS-MXH&C}<#rI(usg za+qxGq+29G>Wc&F;tQ&fjd^E~>}MFK+%88A-zxMhPCLjEt3zhJIV~Z|b%;q#Iqo7T zMuYi6esN{e(XW-c~-SL;Z#gyt7H5uw3|*paS2o0<(Wdws4^-P^gMZ z#BAIEF(nvVAf_zD$Hw)&O;wano`}~b5BABs-fnOrIlzKQc6T?*&ju;-Gf^SbMe}4> zSy_FwK9k?NM$z4h4inMc%9<=~$kLW9KO;*!va~15c|=LKB1=+5XOP#VmQHHvq?S%< z>7lUi%W*w~4cTzpura$c^O z77`r_bAnh0Z>`TDK;~z<1S$TN2RRDwty&(*-&;~Xd{94pe57h(Qqq)FRMpfqG_|zH zjn|nlagwf{zJcN7zZrdEOwQT=`8H8CSF9$C7Kq3-gnL7Fhy6@G5+W=WE9H_afJmBtW0;?;95>Qkm11VvC97Yf?Sv)(Z(xR5|S70txUzv-MnG zNPrhJzFv&&X=}M!c`0crB*58ms!j!v0GH`6xLXSe(6;wld>|yid=E{nN=SfdgH?6O zz`HoEsAoCww&e70D+OL%pU#mPz&p(vO)>*sS?(_iSAe(n=ni%&@FwWRw9kU%yL^Yz zjwax}ew}-#6L|AiFD%{$ylUPv0|Z$OswiUq*?^`V+}AKVc>JTSSAmymxZ@TJcvlF^j68rh zIm@&xXK!1J<@4{B#{loNNa5vt;7!S@sHy>@#5%@Ma7*olOPaUz{Yi zyMZ@Tn0>PWcs&F&mRSIA|GF$Izaksp9lq@Dng+ZZXEKWjULT#)HN=-qX}e|m1K`ye zOj2BWIe;C<2@{Jbf@ zt)*pQdrmg+rvBKJPw=W1+PIVe?`5|tj}^enb`+kk23~UIPluR8c`N38z#S=L7JxZ$ zNb7i&Junx-PF=|?fqBTSbqTigch|=J$JW{kEDQ^OuUW!g`<4}AqW{JSlQUgDVslvP z$KGT7XAJKD!`iS?`JRbYywlGD<$f0U*V);-9pzQM@3k@V#-FjWkJS4?C;PCGefTbE kW7xOeSR2vdf3z*^JM1*}J=TSt*??o`;IEr792{SVd4*%joQg8FIVF)fO0LjpIJMK@G^kM7NuwhThh*xQLKzZ? z*g3jMs8q^a%A7*xWaxdK$Ose%q4SNqaM_;`kRb)~P`xRaiHz#)w zvArYKp~hY-JS8jj?CEGcS9tijIr#WEtb}ImeZ)@7y?p(ARyg^gr5d%AN!!BcsY#-2 z1__r znL+$yj?reNtq9?4zDzsXS;kAFw4?VQ3S~GKW0mZ{2!>=dk5DlS=byeP@w|RS!V}im z!6SJCL()PQXJQ3;6C_V`NE1o5d+2p`??PSpWvf2mw)9ra+ zurp86TDdtNPGSfbr9C1}gCWJvVZ-?()Qg#*6zsQi#4TvI*x7y6#!b*(x2OpxU74IS zzE!3-w)154Hp!}*Wbn7u4i%FvX#eb2ofsUC**iNJuc%u)|7=f&RPPuzu$KtNG4tfE z(RMsppqA#c1>#h-V|}Rw%X7(>TrwjfVf^pvzbyCTEzG4S*_1?vY`GLs-ev<&$ijDb ztUsOQUX;AE?%~UmB9HqANBc4)y;WA5shb$hP6la$eh{x>=lWQU?L3zj(sV9j7f%HJ zEZ*ztGF-TdUZ6|eRaQw5+So7r2voW3I%lMcmZNA~l25A41%TpT|zqML( z&l~F)xKDQV|79JHr>u{+4B}}xRcM;b_y=56*t~^!ZXHrQG&Yte2U^vhP>*AxqO_Of zZUFlt=U-*>x5D_FHx-A3^NlY`!qYT^c>Ph^g5rOV2Y*BMjJMs5@!9Dl+l=Ek?LS(| z2yNc=5AEo~lTHS!w63jz`%^c4XtX=D|18PkybGOo;YYY{N@t$*cU;8F;Let|b6nvD z8RZ38s^;tA=7cFRgFu?7U`EVng}>o4ToknbHOrmFfnr}hM+aY68#>r~i398{y+8-m z*`ZRk?|;)(!WmW$8LPo|#$;r>{zyd-dPrN%`Iqzy`fuqs3DXa|Z#`AtK!(4g=F*f&JC^>A!(RhxZ(vCOrnL-cEdyH1fYvggwG3!216s>aAMFikT|-*ekk&P% zbq#4V3WGf;doa}H`58}e={~VIm z-$LT{r;xC{{$f3vtgQ&K9f9k0!L}+Q#0+!@-sQ+1r41ac$FycDR9 z_BBpLeG;3Og?!?2?=n94a?xI}+SC3NP+{0g@H^;9*8B`=A9MT38PL~s81Vt^iDtbP z_Q!2_=$Hc}j@yH1zh`suVO1f-`HmLqx#AtC@)?jbPH5A1%xj&`VfpjG%nPU&Hfzyw z=K9^rMSH!=eQEoZIu^&!E;8UtCeqLQGHu6r`~EbvyVBM4EK>XGIBMU~y&sK38higV z+NTF>r{m98+eh^h4;Qp2R^EN+I2DDvVAX+kC0abTldP3T^^1NFOGSP73^R-a>0a(m z`%SezD@Xa|SrY}?uUng!!+?Cjy94^g?Y87IfO7p8mOSb^zs%YRvcSVH^^jZSRsy|`y^RN$D=*LEE)Cs^F?(0#7UF3lbSLC z+vmDh$79?qEPK@BI0J_r>4otu3bLC@{kj{OPwl78Yu*8RVIrf3<0r1F?h3Fc>PL3t zcu4*+ReE2IoU(}eb+hV&x+2wZi6e;Zqy z-f6mevi<*Zsr(-jyf-BH7+lZT39JdruoKxytdKQj&DhDTIXi{5V5hRvSW9*~JA<9c z&SGb?bJ)486+4fe&swuK>;l%7U5M)nyNI>>zpN(W=>Ki4VBKgtbd_Me*yXH*^=5s7 zWvn0k=l}o6^CuD9 z4+Bd-h;^vHtwXMy=fVGT8^+VTnI6>*p00>+r|bjXzhrRq$;&`d?Zk{BrX2eRAajp2PSSOVY;Qt;YwoMeLA`wFxM|fu zl4}`*{wmURHln|repBRKAUQyca6BTbofoOU!M7_FJRy?UnvHZ`UiUwj0Wx; zvj0w;UqOUJSS?VL+|!x*>vkjbCWG-Dv;qC)vabF@{pD6S(R|eshui`^d1C#E&TqRj zMfE5@K9+-VlEhsOLVtxR$qT8!r-tN(gI+Nq>jU*y)M*RESJ7rxzZmN8i9>gBeG)b1 z*u6nJpNa(3*W60_ivDs$_qS&Z-B6Wz{$KA-KlXEo{9j(eo@;uSLp%jrCuf%;@3<*w zz7F>qg!ctW*Sv~xCwAWD=|C>N$D4a#N3?b(RRM*wj$Et;a!K=lEr)V)ry=4RQnuQV zlH{!kM0w798SazByYcfLWLiU;-;uofz(V8@mB>p-Q=_9LKz)@ieXza8qR)M4yB<;7 zuVcBF&*vLJ?qka4n?T_;JMV|6FNy1h_l+WB@DsW|43OnFW4Ynos!u?T?+TEs8)ifIje1$HOf2uD_4yd`>&Zb>&fooIH_C&HUhhH@u67j< zFd2J>`c=UX#Qm0Zm}o}hx7fJ_<4M*Y_AI9ME*9&sT>Z|YeaMb|=IjQFY6UNzLb<%+ zVZsxjX7wBPF_4(d>rBV9+5Ik3*mvq|Kxc6`y^OB9|b$2B2oasty>>nscw;B1>a# zetHBXetJhSuJT7#8Bc*30d>!jBsOa5Wn`+;4xBe`?djYypkQCytVRap=(%%|0=pXr zk-`wyggPkau5L-e`rM9f?`b>9daoXUEL?ls5BrsO@A3di!0$&4uq<;N<@KfS9xC(v zPcLTl8;Q>M{ha0@#Ebi_uG@WN>v3=Jd5e=7lRAv!AD3dIbvnIIBXYXX`z+_;;eA*> z_nHaaS2Ua}G2iA+oMieQNVmVzVig(d=$_n?_V!5lp9cB3f zaWl?0IT^GW?S*?5U8H$z?bLBI>Mf?1t99@d? z+vGCb|KPzSn2uwb*7ajN$md7z#q~t;N$?r{<@WvKGp=t$YhFj(=g6pCk8$5pTnitG z?Qq)$Pp18?G`Nhi=B^Rx)Zf&8D{!CX7RcYyd?fA6ym_d9aM~U^4=xc$Ye81*ZE~dk zHlDmyh2?^VnhdO`_{9+YCHdy3P|pon<%s@r6Nh}E{^GidcG*5Fv42vwNOBeB#;#xf z?5`rcDig;=zSf^Pjg&OBjYn>YkIUkLC6(K512Zf|mLGtM`HucLze4TMV>f_Yk4g#p z%Q4#XN{~YjzQuBqne>#lTcNFj{&E9S2IBfp=2dkn}(I>eoZv{nj}HFQ0VVZ{oQ{ihw8VbC1PBNe0%H*WuE_(=ilGYN$*LKJ0X6g zLZBIqEZ85p599XK|MMP{IVNNx%3iC^?Lq##Yuw`W18859&8WheZ!){G}Mm`ADE2tc71pFn%L4pg0g5ka@nc1V9&X`Ers#GbBWhr_^JyR zSGx!)Th)TkHDr0zZ)UOUJmnDLp)9sa(jQXx$ zSH=K^+OfWLz8bWK(ea3!7cB!j5+9jy1o`%tv;#~#ct8^Tx)SSsK6-_YU+{kSew0;@ zXCxp?_XpGQ8|JP(!l*+3+r8F`05mJ8OAno_S zm{-)k;OZbc{*uYh>3vnRJduv4P~ChtPd>LydVPI8#>ZS>v6bOqy)XLqT}5NiFZ-FM zmX=7>S&`5WF<`6d{9e9aL&sUzLqzA@q^>Dl`F{RWo`2uZNjrFchWLs4hj8T>$8j|U zxiC+PnbM)S9@Bjsk9Oj?&BbTzZ6eDtT_)dr`IVN{UxeT4^{w$$um6A zUq#Zm_W1nIv67ASIX@xS^&A6s?LQTsL=u0WQBY0_URcroNbTvhpy%2TH9>zBZd_;~ z@TOC!44*#)@yk|HfBl+bPJ>KRvM-?@Wc}e8=*QU%^|nud!t&Wo7g0ap&45=7w6k^o zdyIqrEUzmVH`1o*KJ^#hhoD?o*H%dTKhS!?Mkp^Fk-CS@FYe=L|Hma)zYv)ZG8fF7 zUq#ldbjSHs9QN6xZ;UIGX@)2}@&loYs4egp{`UhaSzQ+zR$^*_Wrt?+WA<7eF(dlM7 ze!ng&rD!+Lqc@!=ZbQNV?W35SP&=f_pl`oF73Z%br$@cYgK~jl$l~`v5^oW87xan?6;sfTOP{i}5%ioVe6Is4THpB%{pHM# zv?~MFn6|t83`p?(8}+SW-$wSndF>+VGlN&qaloH=ae7DfDtO@LbJf&eozk(jXn){F z7`|@{3v?IX#(J*5?8A15xIUQ1S3P?Y^;d0U;VZP)v8K-vB+BS(ArE#%@6KnSeaZY- z>hFZex@y#S_I*zMSYqIO3uMkH)b2dm4fLFY{&EKA%uuh`{I;PKNL1f%Lw`v#e3t|& zo~;}92<59*I@rIw|DdCEe)0Vq^$oCZBd<(MrT(6fmY+uXkBf6XHl~sXzw~Vz(O*S` z-(Wg_A^W;jfgQKzV+Hkho&E;&SGeR%DlPAEb|$vVJ@2~+{S{uQ&Br*Ao8ER8d7yQd z_4NLFG^H8+<=lpA)_|Vt*~#G=@}1!n^ovv{+n>Sm-_~5E^J|*40QEwnh>;i-}Cg5^54(@bSCk~zpE+F$tcgs?8g04c}_-oP6qA+(8%mFHysp6AOM-+Mn- Vo|93YlfgV!o|E~{%5$;aKLJ~iP1gVb literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestClk/test_diff_clk.pickledlist_sha256 b/tests/unittest_baselines/TestClk/test_diff_clk.pickledlist_sha256 new file mode 100644 index 0000000..23a3923 --- /dev/null +++ b/tests/unittest_baselines/TestClk/test_diff_clk.pickledlist_sha256 @@ -0,0 +1 @@ +481749f75c2bc933595938c60ded2556e65b8afe958f401cbb50194bdf519e90 \ No newline at end of file From 106b475cc705934b9aecbcced817797cab24d353 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Wed, 18 Feb 2026 06:15:28 +0000 Subject: [PATCH 03/17] NPI-4460 implement baselining in test_igslog - needs further work --- tests/test_igslog.py | 57 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/test_igslog.py b/tests/test_igslog.py index 5b8e5da..278c015 100644 --- a/tests/test_igslog.py +++ b/tests/test_igslog.py @@ -2,6 +2,7 @@ from pyfakefs.fake_filesystem_unittest import TestCase from gnssanalysis.gn_io import igslog +from gnssanalysis.gn_utils import UnitTestBaseliner from test_datasets.sitelog_test_data import ( abmf_site_log_v1 as v1_data, abmf_site_log_v2 as v2_data, @@ -20,7 +21,8 @@ def test_determine_log_version(self): self.assertEqual(igslog.determine_log_version(v2_data), "v2.0") # Check that LogVersionError is raised on wrong data - self.assertRaises(igslog.LogVersionError, igslog.determine_log_version, b"Wrong data") + with self.assertRaises(igslog.LogVersionError): + igslog.determine_log_version(b"Wrong data") def test_extract_id_block(self): # Ensure the extract of ID information works and gives correct dome number: @@ -86,6 +88,16 @@ def test_extract_receiver_block(self): # Last receiver should not have an end date assigned (i.e. current): self.assertEqual(v2_receiver_block[-1][-1], b"") + objs_to_verify: list[object] = [v1_receiver_block, v2_receiver_block] + + # Baseline (manually) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objs_to_verify) + + # Verify + self.assertTrue(UnitTestBaseliner.verify(objs_to_verify), "Hash verification should pass") + # TODO update verify() to support required datatypes, so it does not crash if hash changes + def test_extract_antenna_block(self): # Testing version 1: v1_antenna_block = igslog.extract_antenna_block(v1_data, "/example/path") @@ -101,6 +113,16 @@ def test_extract_antenna_block(self): # Last antenna should not have an end date assigned (i.e. current): self.assertEqual(v2_antenna_block[-1][-1], b"") + objs_to_verify: list[object] = [v1_antenna_block, v2_antenna_block] + + # Baseline (manually) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objs_to_verify) + + # Verify + self.assertTrue(UnitTestBaseliner.verify(objs_to_verify), "Hash verification should pass") + # TODO update verify() to support required datatypes, so it does not crash if hash changes + class TestDataParsing(unittest.TestCase): """ @@ -122,6 +144,17 @@ def test_parse_igs_log_data(self): # Check last antenna type: self.assertEqual(v2_data_parsed[-1][2], "TRM57971.00") + objs_to_verify: list[object] = [v1_data_parsed, v2_data_parsed] + + # Baseline (manually) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objs_to_verify) + + # Verify + self.assertTrue(UnitTestBaseliner.verify(objs_to_verify), "Hash verification should pass") + # TODO update verify() to support required datatypes, so it does not crash if hash changes + # TODO check if ndarray has an equivalent to DF.equals() + class TestFileParsing(TestCase): """ @@ -158,3 +191,25 @@ def test_gather_metadata(self): self.assertEqual(record_3.CODE, "AGGO") # Antenna info: test for antenna serial number self.assertEqual(result[2]["S/N"][4], "726722") + + # As the gather_metadata() function we are testing here, reads from a filesystem and outputs a DataFrame, + # running it without pyfakefs isn't practical. So we temporarily suspend patching in order to run baselining. + # See docs here: + # https://pytest-pyfakefs.readthedocs.io/en/latest/convenience.html#suspending-patching + + # Pause fake filesystem patching to allow access to baseline files. + self.fs.pause() + + # Create a generic (object rather than DF) list, and copy elements across + dfs_to_verify: list[object] = [] + dfs_to_verify.extend(result) + + # Baseline (manually) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(dfs_to_verify) + + # Verify + self.assertTrue(UnitTestBaseliner.verify(dfs_to_verify), "Hash verification should pass") + + # Ensure pyfakefs is re-enabled before further tests run + self.fs.resume() From c4faa43a095bd519f356f353cc01827dc232d9b6 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Thu, 19 Feb 2026 07:44:10 +0000 Subject: [PATCH 04/17] NPI-4460 update class names for IGS log parse tests, add baseline files --- tests/test_igslog.py | 6 +++--- .../test_parse_igs_log_data.pickledlist | Bin 0 -> 1914 bytes .../test_parse_igs_log_data.pickledlist_sha256 | 1 + .../test_gather_metadata.pickledlist | Bin 0 -> 3336 bytes .../test_gather_metadata.pickledlist_sha256 | 1 + .../test_extract_antenna_block.pickledlist | Bin 0 -> 681 bytes ...test_extract_antenna_block.pickledlist_sha256 | 1 + .../test_extract_receiver_block.pickledlist | Bin 0 -> 613 bytes ...est_extract_receiver_block.pickledlist_sha256 | 1 + 9 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 tests/unittest_baselines/TestIgsLogDataParsing/test_parse_igs_log_data.pickledlist create mode 100644 tests/unittest_baselines/TestIgsLogDataParsing/test_parse_igs_log_data.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestIgsLogFileParsing/test_gather_metadata.pickledlist create mode 100644 tests/unittest_baselines/TestIgsLogFileParsing/test_gather_metadata.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestIgsLogRegex/test_extract_antenna_block.pickledlist create mode 100644 tests/unittest_baselines/TestIgsLogRegex/test_extract_antenna_block.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestIgsLogRegex/test_extract_receiver_block.pickledlist create mode 100644 tests/unittest_baselines/TestIgsLogRegex/test_extract_receiver_block.pickledlist_sha256 diff --git a/tests/test_igslog.py b/tests/test_igslog.py index 278c015..3095493 100644 --- a/tests/test_igslog.py +++ b/tests/test_igslog.py @@ -10,7 +10,7 @@ ) -class TestRegex(unittest.TestCase): +class TestIgsLogRegex(unittest.TestCase): """ Test the various regex expressions used in the parsing of IGS log files """ @@ -124,7 +124,7 @@ def test_extract_antenna_block(self): # TODO update verify() to support required datatypes, so it does not crash if hash changes -class TestDataParsing(unittest.TestCase): +class TestIgsLogDataParsing(unittest.TestCase): """ Test the integrated functions that gather and parse information from IGS log files """ @@ -156,7 +156,7 @@ def test_parse_igs_log_data(self): # TODO check if ndarray has an equivalent to DF.equals() -class TestFileParsing(TestCase): +class TestIgsLogFileParsing(TestCase): """ Test gather_metadata() """ diff --git a/tests/unittest_baselines/TestIgsLogDataParsing/test_parse_igs_log_data.pickledlist b/tests/unittest_baselines/TestIgsLogDataParsing/test_parse_igs_log_data.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..39e99d601db8ea5571a09023ff1380f8c9b81173 GIT binary patch literal 1914 zcmc&zTWb?R6i%ANG;I)t;&Y%cg|ORmW_EV7zR0G=MK?E+fFel9Wnl}skqa2`!51%u zdF%Ll^=uNgTT`(`%{=VRoHKL2?|d`g`{b8#I<^{b)QNmFS_~&kz1pK}B{Q~#z$*Z#9PP5}M!zZW$t6WYDq<6O2_Nk0uJk&^aXzH87DQw{@h- zDUGeR-#_)=c7~IIe>&;R15{o-I3n26^D`&sHkeTI$gMZ?Zn>6sopQeTw3%-fohFHZ z1rFS{l}u87su)wjad0djGC*4ct|4!s^%&r_`W`bh6fI=y*jC_dvs7>8>b0`dc=a1- z4#+t&>O$y@8V!MeLPucMbPzPa7Mgz-nqP;Gz!0HddfX#%vrehuw3uayw!ulgX5A~% zPYA>TGsZ)W8z2fL!}ax z;Ly@adqC=NfGa2Nh{u^T=Suxw5PUOhCw3D`kbsSMzsGztJNwP|`_1I@)Yrdf=HzvU zUyCmuhTTRu(CWQ@r0w^^PQ>GLo8d6rlKK<=)))Lf#HE5xv(Mu!JiZW&x`(a$K|2a+qK^#jjyi|;v_Q_L zGinc8VZR^VgJFYyRPS{M!~Uo~6p>3ZlE*7Bdl~YC7v>)D?0k)X$;tiK!z)Gr?HHCN^|kqe+Wy5MpQ)x|~;$ zi7BRzhGC9i88)&B)eHz+QXvRrN+%jLURvJ|52E!(tG^!BI{R9?*PL}Wo4v%TD8#W9 z0za6sqF__Oh9cgsD2f=<;<2JogcSoT)YJ?`rMhAe1)6jSQRMp~NNK!U8?|65y91cb zpxvsAwJ6NN`pk=^C@hHQ11=K(`fPbMqEDlNRtpDElN{%A0&vBd<_Y?+;7nUgt77nT4wGHXnce^=Sn7^;0c>nZuN zsL@O%SE?$d;*M9|J88Z=)v8zFBc>gOa9qxpDr@5`-NUQ5DlFwp@gu ze8xUq#YPpAX&*)^!c}Y@`%D#M_*F4-bz|JuRxtrgR|)w3$#`?gtEakC7?RB*{8ZSV z-FZY1(^PWg%wWew7IsY_I!Kx@G16U~9+||Vu3?!;tLai~xYV{1({?F!Tuax_&=(=d zp}c4NVrkB_3cRwnfpXF(D2HvbEg%qf;}jfBxP$reYB`t9ZNHxrh|K#HsMW>FI&j0n z)&~I2ja+`)56T|sD>;8tYHV-fulPVL(l0bQm)jHCU>}K>-}YIpFvseJCDxcmKWnj% z*#T>_&V9~$aDQ?Z{4N%j0l!z`h5eb`?w#j$yE(DjWxx9N8*zPqJvkB_Kc9Yg_-^{? zuh%CM4DdWV5qI3<-_dxoR;j|u^je+HQ{p?H<0)XOjF&5WRbp92pi|fmtL0t8fIVCj zg&{3#VhK|7fYG$F4lMnbMygUOz}}Jai~f{=aM&YKv%)ZRu~lOzVFzOe!!9Wdj4|x* zz$VKUF)aezb5c3`TYz4Wts+a%i!AhnHjtwNy?_sBFYbjOFTBo*T>bSe_rt@jnrB0ht^6`AifhB`t zBrUK44$cHmGeYoq>(wqXV_R;d)QZxuDlWE#$Bjq`Xd+Fjv%ckjK--}nK!6fwAjX9y h-hAW*RXtE5;u7=~^c`R#SG(#Truape=b6v4O7D6dqelP$ literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestIgsLogRegex/test_extract_antenna_block.pickledlist_sha256 b/tests/unittest_baselines/TestIgsLogRegex/test_extract_antenna_block.pickledlist_sha256 new file mode 100644 index 0000000..a0e20de --- /dev/null +++ b/tests/unittest_baselines/TestIgsLogRegex/test_extract_antenna_block.pickledlist_sha256 @@ -0,0 +1 @@ +45e36f43cdbd37e1ea7e5d41cdec62df5af83792ba9588cfbf5ea7c969111573 \ No newline at end of file diff --git a/tests/unittest_baselines/TestIgsLogRegex/test_extract_receiver_block.pickledlist b/tests/unittest_baselines/TestIgsLogRegex/test_extract_receiver_block.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..d22f6cb3042ac3499f35a7f188d4b8f4da9b544e GIT binary patch literal 613 zcmd^*K?;Ik5QQboAld{Sz@;F{%s*P$PN$GSQ2bql&{FUK9-u=^_pPL`TC}KT!&|+@ z`x zYAv2Cz{msgkSbhl0BU)3frnv*s_!~+c&2wnWfyrCmxm7^0wh8uJPv*3Lys9i&!cw` df(KaP@)r=jgI-2ktCQwyRX0z=1Akzu@h``Yn3n(m literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestIgsLogRegex/test_extract_receiver_block.pickledlist_sha256 b/tests/unittest_baselines/TestIgsLogRegex/test_extract_receiver_block.pickledlist_sha256 new file mode 100644 index 0000000..7d0756a --- /dev/null +++ b/tests/unittest_baselines/TestIgsLogRegex/test_extract_receiver_block.pickledlist_sha256 @@ -0,0 +1 @@ +afdd996360daac38e7ac69bd045668134e6a28ed34d703d81771f91ac9a2c23c \ No newline at end of file From 02097625f949a8908152c8a15fa60d5caa257b01 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Mon, 23 Feb 2026 06:37:41 +0000 Subject: [PATCH 05/17] NPI-4460 clean up comments --- tests/test_igslog.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_igslog.py b/tests/test_igslog.py index 3095493..ddb3819 100644 --- a/tests/test_igslog.py +++ b/tests/test_igslog.py @@ -28,7 +28,8 @@ def test_extract_id_block(self): # Ensure the extract of ID information works and gives correct dome number: self.assertEqual(igslog.extract_id_block(v1_data, "/example/path", "ABMF", "v1.0"), ["ABMF", "97103M001"]) self.assertEqual(igslog.extract_id_block(v2_data, "/example/path", "ABMF", "v2.0"), ["ABMF", "97103M001"]) - # Check automatic version determination works as expected: + # Check that automatic version determination is used when a version is not provided. This + # leverages determine_log_version() which is already tested above: self.assertEqual(igslog.extract_id_block(v1_data, "/example/path", "ABMF"), ["ABMF", "97103M001"]) # Check LogVersionError is raised on no data: @@ -44,6 +45,7 @@ def test_extract_id_block(self): def test_extract_location_block(self): # Version 1 Location description results: v1_location_block = igslog.extract_location_block(v1_data, "/example/path", "v1.0") + # NOTE: this test cannot currently support baselining. This will be addressed in NPI-4492 self.assertEqual(v1_location_block.group(1), b"Les Abymes") self.assertEqual(v1_location_block.group(2), b"Guadeloupe") @@ -153,7 +155,6 @@ def test_parse_igs_log_data(self): # Verify self.assertTrue(UnitTestBaseliner.verify(objs_to_verify), "Hash verification should pass") # TODO update verify() to support required datatypes, so it does not crash if hash changes - # TODO check if ndarray has an equivalent to DF.equals() class TestIgsLogFileParsing(TestCase): From 233baf1b40b6f81e42050a58c7ac81f2ef74f255 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Mon, 23 Feb 2026 07:34:47 +0000 Subject: [PATCH 06/17] NPI-4460 improve unexpected warning output --- tests/test_clk.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_clk.py b/tests/test_clk.py index 12ee148..a77ab4d 100644 --- a/tests/test_clk.py +++ b/tests/test_clk.py @@ -3,7 +3,7 @@ import gnssanalysis.gn_io.clk as clk import gnssanalysis.gn_diffaux as gn_diffaux -from gnssanalysis.gn_utils import UnitTestBaseliner +from gnssanalysis.gn_utils import UnitTestBaseliner, stringify_warnings from test_datasets.clk_test_data import ( # first dataset is a truncated version of file IGS0OPSRAP_20240400000_01D_05M_CLK.CLK: @@ -73,7 +73,10 @@ def test_diff_clk(self): self.assertEqual( len(captured_warnings), 9, - "Expected exactly 9 warnings. Check what other warnings are being raised!", + "Expected exactly 9 warnings. Check what other warnings are being raised! Full list below:\n" + + stringify_warnings(captured_warnings), + # Passing the converted warning strings to the assert may not be very efficient. Consider changing if + # it slows things down. ) # Test index is as expected From 1b4afb5e4b930ffba072a2064d7e2fb596440565 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Mon, 23 Feb 2026 07:48:14 +0000 Subject: [PATCH 07/17] NPI-4460 start of baselining for test_sp3 - not yet complete --- tests/test_sp3.py | 113 +++++++++++++++--- .../TestSP3/test_clean_sp3_orb.pickledlist | Bin 0 -> 4103 bytes .../test_clean_sp3_orb.pickledlist_sha256 | 1 + .../test_gen_sp3_fundamentals.pickledlist | Bin 0 -> 9314 bytes ...st_gen_sp3_fundamentals.pickledlist_sha256 | 1 + .../TestSP3/test_get_sp3_comments.pickledlist | Bin 0 -> 3394 bytes .../test_get_sp3_comments.pickledlist_sha256 | 1 + ...ct_svs_read_when_ev_ep_present.pickledlist | Bin 0 -> 3470 bytes ...read_when_ev_ep_present.pickledlist_sha256 | 1 + ...test_read_sp3_header_svs_basic.pickledlist | Bin 0 -> 3325 bytes ...ad_sp3_header_svs_basic.pickledlist_sha256 | 1 + ...t_read_sp3_header_svs_detailed.pickledlist | Bin 0 -> 1579 bytes ...sp3_header_svs_detailed.pickledlist_sha256 | 1 + .../TestSP3/test_read_sp3_pOnly.pickledlist | Bin 0 -> 3388 bytes .../test_read_sp3_pOnly.pickledlist_sha256 | 1 + .../TestSP3/test_read_sp3_pv.pickledlist | Bin 0 -> 3325 bytes .../test_read_sp3_pv.pickledlist_sha256 | 1 + 17 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 tests/unittest_baselines/TestSP3/test_clean_sp3_orb.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_clean_sp3_orb.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestSP3/test_gen_sp3_fundamentals.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_gen_sp3_fundamentals.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestSP3/test_get_sp3_comments.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_get_sp3_comments.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_correct_svs_read_when_ev_ep_present.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_correct_svs_read_when_ev_ep_present.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_basic.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_basic.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_detailed.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_detailed.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_pOnly.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_pOnly.pickledlist_sha256 create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_pv.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_pv.pickledlist_sha256 diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 5d6f39f..ffd7a01 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -8,7 +8,14 @@ from gnssanalysis.filenames import convert_nominal_span, determine_properties_from_filename import gnssanalysis.gn_io.sp3 as sp3 -from gnssanalysis.gn_utils import STRICT_OFF, STRICT_RAISE, STRICT_WARN, stringify_warnings, trim_line_ends +from gnssanalysis.gn_utils import ( + STRICT_OFF, + STRICT_RAISE, + STRICT_WARN, + UnitTestBaseliner, + stringify_warnings, + trim_line_ends, +) from test_datasets.sp3_test_data import ( fake_header_version_a, fake_header_version_b, @@ -98,12 +105,13 @@ def test_check_sp3_version(self): self.assertEqual( len(captured_warnings), 2, - "Expected only 2 warnings. Check what other warnings are being raised!", + "Expected only 2 warnings. Check what other warnings are being raised! Full list below:\n" + + stringify_warnings(captured_warnings), ) # Our best supported version should return True self.assertEqual( - sp3.check_sp3_version(fake_header_version_d), True, "SP3 version d should be considered best supported" + sp3.check_sp3_version(fake_header_version_d), True, "SP3 version d should be considered supported" ) # StrictModes.STRICT_RAISE should cause a *possibly* supported version to raise an exception. @@ -114,6 +122,11 @@ def test_read_sp3_pOnly(self): result = sp3.read_sp3(input_data, pOnly=True, strict_mode=STRICT_OFF) self.assertEqual(len(result), 6) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline([result]) + + self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") + def test_read_sp3_pv(self): result = sp3.read_sp3(input_data, pOnly=False, strict_mode=STRICT_OFF) self.assertEqual(len(result), 6) @@ -122,11 +135,19 @@ def test_read_sp3_pv(self): self.assertEqual(result.attrs["HEADER"]["HEAD"]["DATETIME"], "2007 4 12 0 0 0.00000000") self.assertEqual(result.index[0][0], 229608000) # Same date, as J2000 + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline([result]) + + self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") + def test_read_sp3_pv_with_ev_ep_rows(self): # Expect exception relating to the EV and EP rows (in RAISE mode), as we can't currently handle them properly. with self.assertRaises(NotImplementedError) as raised_exception: sp3.read_sp3(sp3c_example2_data, pOnly=False, strict_mode=STRICT_RAISE, skip_version_check=True) + # Assert that raised exception says what we expect it to + self.assertEqual(raised_exception.exception, "EP and EV flag rows are currently not supported") + def test_read_sp3_header_svs_basic(self): """ Minimal test of reading SVs from header @@ -136,6 +157,12 @@ def test_read_sp3_header_svs_basic(self): self.assertEqual(result.attrs["HEADER"]["SV_INFO"].index[1], "G02", "Second SV should be G02") self.assertEqual(result.attrs["HEADER"]["SV_INFO"].iloc[1], 8, "Second ACC should be 8") + # Somewhat redundant as it tests the same use of the read function as an already basedlined test above + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline([result]) + + self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") + def test_read_sp3_header_svs_detailed(self): """ Test header parser's ability to read SVs and their accuracy codes correctly. Uses separate, artificial @@ -182,6 +209,12 @@ def test_read_sp3_header_svs_detailed(self): end_line2_acc = sv_info.iloc[29] self.assertEqual(end_line2_acc, 18, msg="Last ACC on test line 2 (pos 30) should be 18") + # TODO add support for pandas Series + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline([result]) + + self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") + def test_read_sp3_validation_sv_count_mismatch_header_vs_content(self): with self.assertRaises(ValueError) as context_manager: sp3.read_sp3( @@ -206,6 +239,12 @@ def test_read_sp3_correct_svs_read_when_ev_ep_present(self): parsed_svs_content = sp3.get_unique_svs(result).astype(str).values self.assertEqual(set(parsed_svs_content), set(["G01", "G02", "G03", "G04", "G05"])) + # TODO add support for pandas Index + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline([result, parsed_svs_content]) + + self.assertTrue(UnitTestBaseliner.verify([result, parsed_svs_content]), "Hash verification should pass") + # TODO Add test(s) for correctly reading header fundamentals (ACC, ORB_TYPE, etc.) # TODO add tests for correctly reading the actual content of the SP3 in addition to the header. @@ -214,7 +253,7 @@ def test_read_sp3_overlong_lines(self): Test overlong content line check """ - test_content_no_overlong: bytes = b"""#dV2007 4 12 0 0 0.00000000 2 ORBIT IGS14 BHN ESOC + test_content_overlong: bytes = b"""#dV2007 4 12 0 0 0.00000000 2 ORBIT IGS14 BHN ESOC ## 1422 345600.00000000 900.00000000 54202 0.0000000000000 THIS LINE IS TOO LONG + 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 THIS IS OK......... + 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 TOO LONG AGAIN ...... @@ -227,7 +266,7 @@ def test_read_sp3_overlong_lines(self): with self.assertWarns(Warning) as warning_assessor: with self.assertRaises(ValueError) as read_exception: - sp3.read_sp3(test_content_no_overlong, strictness_comments=STRICT_OFF, strict_mode=STRICT_RAISE) + sp3.read_sp3(test_content_overlong, strictness_comments=STRICT_OFF, strict_mode=STRICT_RAISE) self.assertEqual( str(read_exception.exception), "2 SP3 epoch data lines were overlong and very likely to parse incorrectly.", @@ -428,8 +467,11 @@ def test_clean_sp3_orb(self): Tests cleaning an SP3 DataFrame of duplicates, leading or trailing nodata values, and offline sats """ + objects_to_verify: list = [] + # Create dataframe manually, as read function does deduplication itself. This also makes the test more self-contained sp3_df = TestSP3.get_example_dataframe("dupe_epoch_offline_sat_empty_epoch") + objects_to_verify.append(sp3_df) self.assertTrue( # Alterantively you can use all(array == array) to do an elementwise equality check @@ -454,6 +496,8 @@ def test_clean_sp3_orb(self): with self.assertWarns(Warning) as warning_assessor: sp3_df_no_offline_removal = sp3.clean_sp3_orb(sp3_df, False) + objects_to_verify.append(sp3_df_no_offline_removal) + captured_warnings = warning_assessor.warnings self.assertIn( "Failed to grab filename from sp3 dataframe for error output purposes:", str(captured_warnings[0].message) @@ -461,7 +505,8 @@ def test_clean_sp3_orb(self): self.assertEqual( len(captured_warnings), 1, - "Only expected one warning, about failing to get path. Check what other warnings are being raised!", + "Only expected one warning, about failing to get path. Check what other warnings are being raised. Full list below:\n" + + stringify_warnings(captured_warnings), ) self.assertTrue( @@ -485,22 +530,31 @@ def test_clean_sp3_orb(self): with self.assertWarns(Warning) as warning_assessor: # Now check with offline sat removal enabled too sp3_df_with_offline_removal = sp3.clean_sp3_orb(sp3_df, True) - # Check that we still seem to have one epoch with no dupe sats, and now with the offline sat removed - self.assertTrue( - np.array_equal(sp3_df_with_offline_removal.index.get_level_values(1), ["G01", "G02"]), - "After cleaning there should be no dupe PRNs (and with offline removal, offline sat should be gone)", - ) + + objects_to_verify.append(sp3_df_with_offline_removal) + + # Check that we still seem to have one epoch with no dupe sats, and now with the offline sat removed + self.assertTrue( + np.array_equal(sp3_df_with_offline_removal.index.get_level_values(1), ["G01", "G02"]), + "After cleaning there should be no dupe PRNs (and with offline removal, offline sat should be gone)", + ) + captured_warnings = warning_assessor.warnings self.assertIn( "Failed to grab filename from sp3 dataframe for error output purposes:", str(captured_warnings[0].message) ) self.assertEqual( len(captured_warnings), - 1, # Second warning is about pandas 3 deprecations. + 1, # Second warning is about pandas 3 deprecations. TODO update... "Only expected one warning, about failing to get path. " f"Check all warnings below:\n{stringify_warnings(captured_warnings)}", ) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") + def test_gen_sp3_fundamentals(self): """ Tests that the SP3 header and content generation functions produce output that (apart from trailing @@ -508,20 +562,26 @@ def test_gen_sp3_fundamentals(self): NOTE: leverages read_sp3() to pull in sample data, so is prone to errors in that function. """ + objects_to_verify: list = [] + # Prep the baseline data to test against, including stripping each line of trailing whitespace. baseline_header_lines = trim_line_ends(sp3_test_data_short_cod_final_header).splitlines() baseline_content_lines = trim_line_ends(sp3_test_data_short_cod_final_content).splitlines() + objects_to_verify.extend([baseline_header_lines, baseline_content_lines]) # Note this is suboptimal from a testing standpoint, but for now is a lot easier than manually constructing # the DataFrame. sp3_df = sp3.read_sp3(bytes(sp3_test_data_short_cod_final)) + objects_to_verify.append(sp3_df) generated_sp3_header = sp3.gen_sp3_header(sp3_df, output_comments=True) generated_sp3_content = sp3.gen_sp3_content(sp3_df) + objects_to_verify.extend([generated_sp3_header, generated_sp3_content]) # As with the baseline data, prep the data under test, for comparison. test_header_lines = trim_line_ends(generated_sp3_header).splitlines() test_content_lines = trim_line_ends(generated_sp3_content).splitlines() + objects_to_verify.extend([test_header_lines, test_content_lines]) # TODO maybe we don't want to split the content, just the header @@ -552,6 +612,11 @@ def test_gen_sp3_fundamentals(self): f"Content line {i} didn't match", ) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") + # TODO add tests for correctly generating sp3 output content with gen_sp3_content() and gen_sp3_header() # These tests should include: # - Correct alignment of POS, CLK, STDPOS STDCLK, (not velocity yet), FLAGS @@ -563,6 +628,9 @@ def test_gen_sp3_fundamentals(self): def test_get_sp3_comments(self): # Somewhat standalone test to check fetching of SP3 comments from a DataFrame + + objects_to_verify: list = [] + expected_comments = [ "/* EUROPEAN SPACE OPERATIONS CENTRE - DARMSTADT, GERMANY", "/* ---------------------------------------------------------", @@ -570,8 +638,25 @@ def test_get_sp3_comments(self): "/* PCV:IGS14_2022 OL/AL:EOT11A NONE YN ORB:CoN CLK:CoN", ] sp3_df: pd.DataFrame = sp3.read_sp3(input_data, strict_mode=STRICT_OFF) - self.assertEqual(sp3.get_sp3_comments(sp3_df), expected_comments, "SP3 comments read should match expectation") - self.assertEqual(sp3_df.attrs["COMMENTS"], expected_comments, "Manual read of SP3 comments should match") + automated_comment_read = sp3.get_sp3_comments(sp3_df) + manual_comment_read = sp3_df.attrs["COMMENTS"] + + self.assertEqual(automated_comment_read, expected_comments, "SP3 comments read should match expectation") + self.assertEqual(manual_comment_read, expected_comments, "Manual read of SP3 comments should match") + self.assertEqual( + id(automated_comment_read), + id(manual_comment_read), + "Manual and automated comment read should return the same object", + ) + + # We don't pass the second comment object here, as we have verified it is the same object, and the + # verifier will raise on duplicates. + objects_to_verify.extend([sp3_df, automated_comment_read]) + + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") def test_update_sp3_comments(self): # Somewhat standalone test to check updating SP3 comments in a DataFrame diff --git a/tests/unittest_baselines/TestSP3/test_clean_sp3_orb.pickledlist b/tests/unittest_baselines/TestSP3/test_clean_sp3_orb.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..e8be54e9e89fd7277db004f0e6e81b29e98ce8c6 GIT binary patch literal 4103 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 literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_basic.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_basic.pickledlist_sha256 new file mode 100644 index 0000000..1a52a79 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_basic.pickledlist_sha256 @@ -0,0 +1 @@ +a7087d6d1ce09694262ed7915f13d622b9381e1f82b2aeda39eb11c87adbcb6d \ No newline at end of file diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_detailed.pickledlist b/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_detailed.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..74c1d8b2c3f575bc48b8206e362c0dcc0ffa2eb4 GIT binary patch literal 1579 zcmc&!TT|0O6z(mhTm(f$RK$q4_9Zp}1)rQ+O0AK!LkcrKFgx33B^}e0$pvIa9DPt> zX5U6h7O6Pw!ISp`q3t1WXHA&|{(C zc{>?=5v32}P!xHN^mhWm=l^u>`4J0z>V;aL`gDf{0Gzga{*K3|a%X@5(d7;=AMR(Gj;L4wi zIfq76@EJeu?;dCb)5J(L2PYk3VI4Y0%bv$FTzZH6xDP`d=?v` zXr|4~N8!$K#fc7fnS^ATE8_SlS2=hHqDR{RNq!|`VzBX`!l?#|0yU3Iu;2|kJ#B~z&iK3Z)yH&%)L(uVPBrClML zb`4?|U|efA)@v}m)_74P?UyYWXX|AH?IPMGv=7lfLc5H11?^*L^)gx=En;=V>WI}5 zt0PuNtd3Y6u?Aud#2Sb-5NjaTK&*jS15pN|77?|Gs6{{(EK+MVtIr_ALUZHkuS`Z- zTV(A;r7rjsl(E{}wA#dOL((#NnRrr@k*(T>-Dp}^WA{N)UdEN~z<~yropNS{@A6Df zKjG3Xon|s6nXHzQ%Y!!k&qsj&*&BHfrg%4dJCnYX%H6-GK{N;)76=Qv6uJfzu`GtYL$}4*TP0nSb_7qi1fMg? z#*(V)sarGlSc)zjTIn6fqFOE@NU#?Bz#SNW$Tp+1cB0L>n&g<1B{tt=G4D_ z&%eVpFvtJmGA(a_Jou!0LH7McNzCs%urTE9Wa{B?in-2TNtCtyFBG|Zmb(wf`c26p zNNHKqmoz<5Q3{GD+;N`oNP(2}LDoFKN4;1syyFFsSpsVzG;=5JfzbIdR6?)Ul}cl# z%#xXwk`U84gxY~Hg9yPcL**VNeHKyFJWyPPptw-3&?o|Zmgga_Ji3F!E7bD_`yp}S VU5HO3Mm*Y)LV6Us->kSpwVyn)1$O`d literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_detailed.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_detailed.pickledlist_sha256 new file mode 100644 index 0000000..8df4b1b --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_read_sp3_header_svs_detailed.pickledlist_sha256 @@ -0,0 +1 @@ +f58037c1fe36f353ceb5ecae17aac3c07fbb4fa44245fdd36838a207d7d27ac4 \ No newline at end of file diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_pOnly.pickledlist b/tests/unittest_baselines/TestSP3/test_read_sp3_pOnly.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..0d1b3e92dd9bbe13407aca7c5d35e62c464ac4bf GIT binary patch literal 3388 zcmcguTWl0n7~a|Y_F{n`LQ-MyLW}mkQfs*Mjjw)P?KWhqCkQPeZe;qA2h@lUx*j*KQo&R))3 z{_}tTeCPka^S|vs8E^L3#~zGU4lA{iVnmB|U5)P5l_3?QNJ=r4F8h1}Z$69<;Wp%# zhX!?w*3UIB*GyHfDODpnq|}r_RW~qd>a5m_`!dcoo`9Karl-i&a>0ns%)zKdwra!W z;=ZaX7sw>AMr&%;(C}zfw!;~+s%BZyb!8O%%DP&t*9=p)iYBoP*p?WDAbi_o0`s1u zn57E%9h}2$JO!WNF|4gXfs#2otddFIy|3Z%G3U7tQ9H392)iG^VgKW#wbk>3Yz5exyZ+pCl)66IzUh?>Pu-nL zBEIj=?aS9E$J;j5&+Ajkjh~(R=J1avO4b8pF>Ao;XH;4?b~>ARl_-7$H8qJdAb7 zE6yvcgAbL}E>c!|Knf@tYX{tq>MVPhk1P+yBre>^WSn*o;Ts;$K?C3Tcn%u*rnwGG zOx|_ve5EX@2UH_kPz=!52Dzs0m(w-4M?`7eVwSe{foXZpW#>NiI!4q0{NAvUwF?fY ziUP%YNp)Uqfl4+3F63D`J}cjvwU~G}5e4wVLBU~H{i42Zjk8UGV8}y$l-K7cmMV%E zb=|D%C5Tyo4F(vo!x044<>f=d%SR6POL0WVCb7<`3GLa%VqKej_nf^Ge|Ov&drml` z9rpzk*qw;QV%WuA3eP*cCf%_(ENXWwF-r2(j?NQ%iba-YP3+c2DS`E-VZ+g<5iPRLfL+q)F*UE5E(1>33*w{gEQikICQW={aj8;i^9CHms({K zGpzD--)HZVOO=Of+7$7Hyj0lZZ)^wz=dHP04+?O;AfIO0p+E$vevDcni(jSb?Q}dr z(=q28g~YZGFxdu40dJosx|Hsg;@g3w&K?1BiIYSin92~=vOp4*z_2NWiC_`z#n!v> z9Od!2nliJ9mG(Jz|6h&{K_t4e|FB4b8n9IOQk8dz4$WH=!&+1ppg)|Y6S=K6ACmSY53$}%iR!+DO$ zL(WMw3zj*KZl_aBE(6(_%D+r^bGZy7fVA&~$?cDNV6=l^OWAESDI4%A5L7NjcMi}3 zljTH-?qqrdIxmX7G~Jfs@_?Hb?7%iV;90i6!%ihR5lbX!v3D!e+rf$Xc$|T^5=4Q6 pfu;up8WOLAtqU~lW@N-F@((MfhLHzbmG^8SE5~6NGpvFV{TJNTmbCx? literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_pOnly.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_read_sp3_pOnly.pickledlist_sha256 new file mode 100644 index 0000000..1e75300 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_read_sp3_pOnly.pickledlist_sha256 @@ -0,0 +1 @@ +59ae881664375f40267e6269ba3a91a4224d3595be4c11a5ffe1b49dd8b46c89 \ No newline at end of file diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_pv.pickledlist b/tests/unittest_baselines/TestSP3/test_read_sp3_pv.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..88659eb9b3c1ad54e64d243b1ea3faf379132d78 GIT binary patch literal 3325 zcmdT`eQXnD81Hp$*Nx$DnS2;N4pFj!l(rxrV}|Rlx2<%&YkFm2Q*vInV^`?fd2K-q z1V#V{zG!f-_z&?9|3QIB3`FLTSX91bC?-t95HJ`MQDTflB_Pb_y`yYno1%Z1@V348 z?)tpX^SsaR_dM^j-tzjd1txtg#pvM{Io>EI9SsRhaV*p1W(A`>ubh%A_4{^Q+KSiW z5@eB@n>36j_7{)FQ;HUsV@XG|9G9CEEs0SSG-qL3LpM$7!%=7%BUKsktZ%X0F zmWuV1$@JqB$K0ncAIy9|y%q2Don*SLA9jt}u{ZPQ$J2eE9XZZ8-}JEi?CqHot9vIM zKX#Nkz3|4=P1LE(jHz=bJ#@93LH@-(XOEuBY$=(V*spanli%HWxb@4f%$m;5hRtWr zGAlN`eREM;H?ubHk@2rpbugB9iWi(Z)6K}+_Fs9w`Utb3_PG=Jhq{@TpZ86?HTf80 zz;%6HcHVyNc5mV1x9|IXcHM(AV?YAgBk3qGAP%guCS%bC0-@gouu=p;znNUs0<};r zBFL-7192a3AXzkKedPqy!+ai}(SF2rIJ33u*>BJ9>S2EQd4rylxpS9&W!xs zja5qNN0~2v|6ycd#{uT~eO=FVmtSBWo3muzmA%`U*1T^z_k4Ap*?IBk&68JmW!Bv+ zYpMF}9HU+A?OnIg;kn=WK|yRdKM9iR6aa11Kfz|v({&s&3j>mL%{@uFkR<6+s7Mrz z)$)JyQ4Ci``HTCz@)9P>#ty7&jmis3(h-r9P!02?xUx#}#o-;PA>)RU)JqkJ21v=u zijuNvizzFWSd!RKD^)MD_W#q;2z3?@CU6e0d7i$<98L@a`zvcaRU$??V%UqxE|4Wo z3N@eJOj4jy<-wF19IU9IPZ3+=$7lpg3a!MgRN^V{)_4kPN+U*gDA`JSr2;iRU(h1a zlw`cO)v`4L3v11_OUR{;T^;KbY z{tz{oEhcm_!^qAQ44q0t>U?nvTb!3a!bE5l-WFVg&+F(8D^HVIO*l9F`>LmOe0Qz|( zLfnx}G;Xqug=$lCQIQypH^r2DS_kMnN|q9O-w!-};j$3>&=`odl8>(x2GH|wa%JS* zc$+;eY?T#+kXI7xAn)?Lp0Gz+EOMaM4);b982FC&e^CoQLw zQtIl2T*31v())rARS3LJpR;mslYX;P|kppJ-(>r_nUcEe;7m&coAU(8F>x+=o12pqEIqAQ|H58MN0E3IGwk z;V0=TE)?+aP}R#}@r?gEFj_!ti@{km$q>-W6RVt;uBfAVPmmKty24Y<(_ulVq3IGY z7Y4gNUU#h2Fdk%U%XHRAZl~K#3pF!6HD#O-cDX#TD_-C^m}t6=r-5T-Y=WoZfFm4@z33s-D`8UO$Q literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_pv.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_read_sp3_pv.pickledlist_sha256 new file mode 100644 index 0000000..1a52a79 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_read_sp3_pv.pickledlist_sha256 @@ -0,0 +1 @@ +a7087d6d1ce09694262ed7915f13d622b9381e1f82b2aeda39eb11c87adbcb6d \ No newline at end of file From 167afe38166d2c0e14428470b2dca4ec680b4837 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:18:54 +0000 Subject: [PATCH 08/17] NPI-4460 add baselining to test_update_sp3_comments() --- tests/test_sp3.py | 29 +++++++++++++++++- .../test_update_sp3_comments.pickledlist | Bin 0 -> 751 bytes ...est_update_sp3_comments.pickledlist_sha256 | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/unittest_baselines/TestSP3/test_update_sp3_comments.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_update_sp3_comments.pickledlist_sha256 diff --git a/tests/test_sp3.py b/tests/test_sp3.py index ffd7a01..200f162 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -660,6 +660,9 @@ def test_get_sp3_comments(self): def test_update_sp3_comments(self): # Somewhat standalone test to check updating SP3 comments in a DataFrame + + objects_to_verify: list = [] + expected_comments = [ "/* EUROPEAN SPACE OPERATIONS CENTRE - DARMSTADT, GERMANY", "/* ---------------------------------------------------------", @@ -670,6 +673,7 @@ def test_update_sp3_comments(self): sp3_df: pd.DataFrame = sp3.read_sp3(input_data, strict_mode=STRICT_OFF) # Load DataFrame # Read comments directly from DataFrame to check they are as expected self.assertEqual(sp3_df.attrs["COMMENTS"], expected_comments, "SP3 initial comments read were not as expected") + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) # Append list of comments (do not unpack elements) # Introduce invalid but not overlong comment to check lead-in writing part of validation sp3_df.attrs["COMMENTS"] = [ @@ -684,6 +688,7 @@ def test_update_sp3_comments(self): ["/* malformed comment is missing lead-in", "/* malformed comment is missing space", "/* ", "/* "], "Lead in and spacing should be added to existing comments if missing", ) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) # Introduce overlong comment to check exception handling part of validation sp3_df.attrs["COMMENTS"] = [ @@ -707,24 +712,31 @@ def test_update_sp3_comments(self): "/* ", "Padding comment expected on second line", ) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) # Check deletion of all comments sp3.update_sp3_comments(sp3_df, ammend=False) self.assertEqual( sp3_df.attrs["COMMENTS"], ["/* ", "/* ", "/* ", "/* "], - "Should be no comments besides 4 padding ones, after running ammend with no input", + "Should be no comments besides 4 padding ones, after running with ammend=False and no input", ) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) # Write initial comment lines sp3.update_sp3_comments(sp3_df, comment_lines=["line 1", "line 2", "line 3", "line 4"], ammend=False) self.assertEqual(sp3_df.attrs["COMMENTS"], ["/* line 1", "/* line 2", "/* line 3", "/* line 4"]) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) # Write more lines sp3.update_sp3_comments(sp3_df, comment_lines=["line 5", "line 6"], ammend=True) self.assertEqual( sp3_df.attrs["COMMENTS"], ["/* line 1", "/* line 2", "/* line 3", "/* line 4", "/* line 5", "/* line 6"] ) + # NOTE: Creating a new list captures the immutable strings it contains, at this point in time. Without + # constructing a new list, we would just capture a reference to the list itself (which is added to rather + # than replaced when ammend=True) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) # Write more lines, free form sp3.update_sp3_comments(sp3_df, comment_string="arbitrary length line", ammend=True) @@ -732,6 +744,7 @@ def test_update_sp3_comments(self): sp3_df.attrs["COMMENTS"], ["/* line 1", "/* line 2", "/* line 3", "/* line 4", "/* line 5", "/* line 6", "/* arbitrary length line"], ) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) # Write more lines, both modes at once sp3.update_sp3_comments(sp3_df, comment_lines=["line 8"], comment_string="some other comment", ammend=True) @@ -749,7 +762,9 @@ def test_update_sp3_comments(self): "/* some other comment", ], ) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) + # Same as above but truncating existing comments (starting again) sp3.update_sp3_comments(sp3_df, comment_lines=["new line"], comment_string="some new comment", ammend=False) self.assertEqual( sp3_df.attrs["COMMENTS"], @@ -760,7 +775,9 @@ def test_update_sp3_comments(self): "/* ", ], ) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) + # And free form string mode with no ammending (truncate) sp3.update_sp3_comments(sp3_df, comment_string="some other new comment", ammend=False) self.assertEqual( sp3_df.attrs["COMMENTS"], @@ -771,6 +788,16 @@ def test_update_sp3_comments(self): "/* ", ], ) + objects_to_verify.append(list(sp3_df.attrs["COMMENTS"])) + + # NOTE: comment reflow not tested above. This is done in test_sp3_comment_reflow() + + # NOTE: all key changes are explicitly checked with asserts above: baselining is not strictly necessary. + + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") def test_sp3_comment_validation_standalone(self): diff --git a/tests/unittest_baselines/TestSP3/test_update_sp3_comments.pickledlist b/tests/unittest_baselines/TestSP3/test_update_sp3_comments.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..01a05102c95184c8a2c837390476feb081787ed0 GIT binary patch literal 751 zcmbV}+e*Vg5Qas)DGDBodN(&>6|453NcCo$uC*9>zImT_8ZHJwuRqP$fu12;E`S)<%7l?ZmlIHMQ<+6=f|uNe^o!#ql( z7HRVnL)ou<-)t^u2y+0Ork(Q)RQ6}488vz6L=Pi`@WBw Y8z|FXuaBeOnp_&3_I*BUa1>(W1sV|K+W-In literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_update_sp3_comments.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_update_sp3_comments.pickledlist_sha256 new file mode 100644 index 0000000..f0c5b91 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_update_sp3_comments.pickledlist_sha256 @@ -0,0 +1 @@ +61389ff40aedf4db6ac3cdff76ba03663a1d01097f5d1d7bb391760aee96bd0a \ No newline at end of file From 4db660a1fff2079fc8bb43b11f99071e2532e1ee Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:22:07 +0000 Subject: [PATCH 09/17] NPI-4460 add warnings to baselining lines --- tests/test_sp3.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 200f162..4d19950 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -123,7 +123,7 @@ def test_read_sp3_pOnly(self): self.assertEqual(len(result), 6) # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline([result]) + # UnitTestBaseliner.create_baseline([result]) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") @@ -136,7 +136,7 @@ def test_read_sp3_pv(self): self.assertEqual(result.index[0][0], 229608000) # Same date, as J2000 # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline([result]) + # UnitTestBaseliner.create_baseline([result]) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") @@ -159,7 +159,7 @@ def test_read_sp3_header_svs_basic(self): # Somewhat redundant as it tests the same use of the read function as an already basedlined test above # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline([result]) + # UnitTestBaseliner.create_baseline([result]) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") @@ -211,7 +211,7 @@ def test_read_sp3_header_svs_detailed(self): # TODO add support for pandas Series # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline([result]) + # UnitTestBaseliner.create_baseline([result]) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify([result]), "Hash verification should pass") @@ -241,7 +241,7 @@ def test_read_sp3_correct_svs_read_when_ev_ep_present(self): # TODO add support for pandas Index # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline([result, parsed_svs_content]) + # UnitTestBaseliner.create_baseline([result, parsed_svs_content]) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify([result, parsed_svs_content]), "Hash verification should pass") @@ -551,7 +551,7 @@ def test_clean_sp3_orb(self): ) # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline(objects_to_verify) + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") @@ -613,7 +613,7 @@ def test_gen_sp3_fundamentals(self): ) # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline(objects_to_verify) + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") @@ -654,7 +654,7 @@ def test_get_sp3_comments(self): objects_to_verify.extend([sp3_df, automated_comment_read]) # UnitTestBaseliner.mode = "baseline" - # UnitTestBaseliner.create_baseline(objects_to_verify) + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") From 9b99511c2ae44e8ecff8d4c1a5e1a9ed2229307a Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:47:02 +0000 Subject: [PATCH 10/17] NPI-4460 update overlong sp3 content line test to ensure only first 5 offending lines are printed. Add baselining to this test. Small update to baseliner to make type hinting less unnecessarily restrictive. --- gnssanalysis/gn_utils.py | 5 +++-- tests/test_sp3.py | 19 ++++++++++++------ .../test_read_sp3_overlong_lines.pickledlist | Bin 0 -> 1108 bytes ...read_sp3_overlong_lines.pickledlist_sha256 | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_overlong_lines.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_read_sp3_overlong_lines.pickledlist_sha256 diff --git a/gnssanalysis/gn_utils.py b/gnssanalysis/gn_utils.py index 9c805f8..8e2dd4e 100644 --- a/gnssanalysis/gn_utils.py +++ b/gnssanalysis/gn_utils.py @@ -1148,7 +1148,7 @@ def ensure_unique_objects(objects: list[object]) -> None: @staticmethod def create_baseline( # Was baseline_pickled_df_list_and_hash() - current_object_list: list[object], + current_object_list: list, # Any kind of object is ok # These are used to describe the calling class and function, and are inferred automatically. If needed they # can be explicitly set here: subdir: Optional[_pathlib.Path] = None, @@ -1225,7 +1225,8 @@ def create_baseline( # Was baseline_pickled_df_list_and_hash() @staticmethod def verify( # Was create_and_verify_pickled_df_list() - current_object_list: list[object], + current_object_list: list, # Can be any type of object (though diff output only supported for some types) + # TODO update to output notice rather than crashing, if type encountered we can't print a diff for. # parent_dir: _pathlib.Path = BASELINE_DATAFRAME_RECORDS_DIR_ROOT_RELATIVE, # Option to strictly enforce that a baseline must exist for anything this function is invoked to check: raise_for_missing_baseline: bool = False, diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 4d19950..b0d3d9b 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -256,23 +256,25 @@ def test_read_sp3_overlong_lines(self): test_content_overlong: bytes = b"""#dV2007 4 12 0 0 0.00000000 2 ORBIT IGS14 BHN ESOC ## 1422 345600.00000000 900.00000000 54202 0.0000000000000 THIS LINE IS TOO LONG + 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 THIS IS OK......... -+ 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 TOO LONG AGAIN ...... ++ 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 TOO LONG AGAIN 2...... ++ 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 TOO LONG AGAIN 3...... ++ 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 TOO LONG AGAIN 4...... ++ 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 TOO LONG AGAIN 5...... ++ 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 TOO LONG AGAIN 6...... """ - # test_content_no_overlong: bytes = b"""#dV2007 4 12 0 0 0.00000000 2 ORBIT IGS14 BHN ESOC - # + 2 G01G02 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 THIS IS OK......... - # """ - # sp3.read_sp3(test_content_no_overlong) with self.assertWarns(Warning) as warning_assessor: with self.assertRaises(ValueError) as read_exception: sp3.read_sp3(test_content_overlong, strictness_comments=STRICT_OFF, strict_mode=STRICT_RAISE) self.assertEqual( str(read_exception.exception), - "2 SP3 epoch data lines were overlong and very likely to parse incorrectly.", + "6 SP3 epoch data lines were overlong and very likely to parse incorrectly.", ) captured_warnings = warning_assessor.warnings self.assertIn("Line of SP3 input exceeded max width:", str(captured_warnings[0].message)) + self.assertIn("TOO LONG AGAIN 5......", str(captured_warnings[4].message)) + self.assertEqual(len(captured_warnings), 5, "Only the first 5 overlong SP3 content lines should be printed") # # Assert that it still warns by default (NOTE: we can't test this with above example data, as it doens't # # contain a full header) @@ -283,6 +285,11 @@ def test_read_sp3_overlong_lines(self): # str(read_warning.msg), "2 SP3 epoch data lines were overlong and very likely to parse incorrectly." # ) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(captured_warnings) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(captured_warnings), "Hash verification should pass") + def test_read_sp3_misalignment_check(self): """ Test that misaligned columns raise an error (currently only in STRICT mode). diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_overlong_lines.pickledlist b/tests/unittest_baselines/TestSP3/test_read_sp3_overlong_lines.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..93b2f47377855434c0b7e7c4fe60316dbbcb2564 GIT binary patch literal 1108 zcmdT>&riZI6gGk2h!KDO0WaZz#<-0QO!OoNgG*o-!FV!cgR(Xm-Lh^0G$wlR)|>Tz z^=M%bC0^8%ytH{g`nCPu_nxwER(5S>cjAC6BPXJSdQ9-bZ5lQaV~&T!t2lfXj}o&! zoL9KwCd7{kmFSHdhNAfm3FY?o)aT4h$aH( zxiTO=qRtQrey-tQh_u8_Xq;#siE-i{M<@)K=26)xb^Hk<-;yo~G!~j_I1xPO%id@p zn(rB&oxUt=o}JdD&#@qhdef-TT`=gTKQBp?xHyyQiF1>nr6QpJ zVl5PcTD@l3V5l=o^}?Gtj|+GUZ_BRmj*WM1yl3P6<-z^~*5*pGuB;^M>R(tR^#ikB BLu>#5 literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_read_sp3_overlong_lines.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_read_sp3_overlong_lines.pickledlist_sha256 new file mode 100644 index 0000000..d8278b7 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_read_sp3_overlong_lines.pickledlist_sha256 @@ -0,0 +1 @@ +774d383a3aec8cfe9cfe889b50df526e8b91e3cf82684fdddf9c1d911b5d49ac \ No newline at end of file From 102cf02240ca9f1092cf824de87ef888af89bb57 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:57:58 +0000 Subject: [PATCH 11/17] NPI-4460 tidy up existing unit test --- tests/test_sp3.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_sp3.py b/tests/test_sp3.py index b0d3d9b..47b4db1 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -292,9 +292,9 @@ def test_read_sp3_overlong_lines(self): def test_read_sp3_misalignment_check(self): """ - Test that misaligned columns raise an error (currently only in STRICT mode). - Strictness of comment checking is set to OFF, as the test data has a comment line equal to '*/' not '*/ ' + Test that misaligned columns raise an error in strict_mode=RAISE (by default it's a warning). """ + # NOTE: Strictness of *comment* checking is set to OFF, as the test data has a comment line equal to '*/' not '*/ ' with self.assertRaises(ValueError) as read_exception: sp3.read_sp3(sp3_test_data_misaligned_columns, strict_mode=STRICT_RAISE, strictness_comments=STRICT_OFF) self.assertEqual( @@ -306,26 +306,27 @@ def test_sp3_block_column_check_standalone(self): """ Test that misaligned columns in an epoch block raise an error (currently only in STRICT mode) """ - # Check that misaligned (but artificially not overlong) data line, raises exception - with self.assertRaises(ValueError) as misaligned_ex: - data = """ + + data = """ PG06 -16988.173766 -1949.602010 -20295.348670 13551.688732 PG07 -2270.179246 -18040.766586 19792.234454 13925.747073 PG08-538216.0254931012968.294871-1053208.82032548447864.338317 PG09 -7083.058359 -25531.577633 -1359.151582 14650.575917 """ + # Check that misaligned (but artificially not overlong) data line, raises exception + with self.assertRaises(ValueError) as misaligned_ex: sp3._check_column_alignment_of_sp3_block("* 2025 6 17 6 0 0.00000000", data, strict_mode=STRICT_RAISE) self.assertEqual( "Misaligned data line (unused column did not contain a space): 'PG08-538216.0254931012968.294871-1053208.82032548447864.338317 '", str(misaligned_ex.exception), ) - # Check that misaligned data line (flags) trimmed to 80 chars, raises exception - with self.assertRaises(ValueError) as misaligned_flags: - data = """ + data = """ PG06 -5247.775383 -25963.469495 -106.156584 15892.813576 P P PG07-1245784.756055 252424.937619-521507.7748633049872.304950 P """ + # Check that misaligned data line (flags) trimmed to 80 chars, raises exception + with self.assertRaises(ValueError) as misaligned_flags: sp3._check_column_alignment_of_sp3_block("* 2025 6 17 6 0 0.00000000", data, strict_mode=STRICT_RAISE) self.assertEqual( "Misaligned data line (unused column did not contain a space): 'PG07-1245784.756055 252424.937619-521507.7748633049872.304950 P '", From f8d74bf5d06dfb5d63e7a36335e66b2747a00cec Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 08:19:42 +0000 Subject: [PATCH 12/17] NPI-4460 add baselining for sample DF generator. Note this is not a full test yet. --- tests/test_sp3.py | 36 +++++++++++++++++- ...baseline_get_example_dataframe.pickledlist | Bin 0 -> 5844 bytes ...e_get_example_dataframe.pickledlist_sha256 | 1 + 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/unittest_baselines/TestSP3/baseline_get_example_dataframe.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/baseline_get_example_dataframe.pickledlist_sha256 diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 47b4db1..b1d05bd 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -466,10 +466,44 @@ def get_example_dataframe(template_name: str = "normal", include_simple_header: ], ) - # Merge SV table and header, and store as 'HEADER' + # Merge SV table and header into a single Series object, and store that as 'HEADER' df.attrs["HEADER"] = pd.concat([sp3_heading, sv_tbl], keys=["HEAD", "SV_INFO"], axis=0) return df + def baseline_get_example_dataframe(self): + + # NOTE: this function creates a baseline, but does not do any testing beyond that. + # I.e. it will detect regressions, but does not assert that the starting value is correct. + + # TODO enable these once the default template is implemented + # ex_df_default = TestSP3.get_example_dataframe() + # ex_df_default_no_header = TestSP3.get_example_dataframe(include_simple_header=False) + + ex_df_dupe = TestSP3.get_example_dataframe(template_name="dupe_epoch_offline_sat_empty_epoch") + ex_df_dupe_no_header = TestSP3.get_example_dataframe( + template_name="dupe_epoch_offline_sat_empty_epoch", include_simple_header=False + ) + + ex_df_offline_nan = TestSP3.get_example_dataframe(template_name="offline_sat_nan") + ex_df_offline_zero = TestSP3.get_example_dataframe(template_name="offline_sat_zero") + + objects_to_verify: list = [ + ex_df_dupe, + ex_df_dupe_no_header, + ex_df_offline_nan, + ex_df_offline_zero, + ] + + # TODO baseline outputs + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") + + # TODO implement the following to actually test the example DF function, not just check for regressions against + # the current value + # def test_get_example_dataframe(self): + def test_clean_sp3_orb(self): """ Tests cleaning an SP3 DataFrame of duplicates, leading or trailing nodata values, and offline sats diff --git a/tests/unittest_baselines/TestSP3/baseline_get_example_dataframe.pickledlist b/tests/unittest_baselines/TestSP3/baseline_get_example_dataframe.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..40986f9858dc324f339de08251b87bbf84a9cc8b GIT binary patch literal 5844 zcmeHLTWcFf6xQlyDK|Swn>NsvKwsPos-1+=hhCz{vg0UT8A%CETC=RAiI0@k%Ir!` z4Gm5Rxfq7NWD02-0(~fX3N&vmy}36^ANtf%`VeTze<*~$wCBufELqY9_mTvHWjv!f zduHa$Ip2KetWJmD{b5HyerCil-#Ta7Wz$WR>JCdBcgz|SJU(VN%yIesl6dBfIE8v) zqc-aZ{#2)W#cnXiHZ3<%Gi`I0Ij-QF_FMJRi79U^E(yM)HN&tfMK`fBMesqRY0p(k zCoE`VC=)*~V2DS`jfFWzlY+-z5=$4o=OM1WEOcEz_!oX;lL<6LaVhodCp+!s zH?G~>=hu7A>u&EXdXjdZyn11K?&n{Y{rc_|y63BLt3zF8Z!tGfG+hM92E%42jY%72R0&B@i_J63r9I$zc$P;xABM{aJpqHTjiL#Us90(^*vW(0Rq=bfD}Jw0{AL6X zrSk=0(rLps0a|aK$5Y{EdvSkwv3K}T1~zUAi<1f*oEmu9ATdK+hCz;u?(t$AZABNvJveKc*56ZYHBAc!mA{~@1EXc<;jHH&Mlna)!n==* zMzB)sX4F{2lsH4IM9fA5dFzzm(F5t^SQ_VS>x8q*oC><0NAunnMTiiE0lqC?v1cvT zMR~}W!!rc8wMNK;UYPbrLdzd9CiU^`D*W9AyIWSU8%(C~q^WE+H)iCI=84CXg`{yf zpT=cl_&r^ioJym3eDYA*C>)(m3s{8HaE8ZpBR!o>9l$xUY;M2zj1Np7F~$!iCy=Vq zrd0N@UNG_nT!xmTrMz7Rj-+$>$*jK0V80-fwHV|rUXW$$8)>Dxc|#}`)Z&5grj3#4 zfV>_m-0C`*=iGRcwWJlk)-%w1_j^xKmbiaK?SRYA@?oEs$>@l;O zNbCZx^QcV2L^C9g&YM<~9^B$FO#KF?XdFC^C*9)Zil*6giOVkYXzP-|6}nJ{oL=^>L*>J+?>O-V>Xy!nMKM27v1VsEfCO~MYp;gznl`)N3px;Hj5DQ zst>$uaoE%wVK2i!@sQ_i>Ga+N-5+Q@w}3Jy5ey_LBPz_bVc0Wah0Ea(Oo6^C4f;?w zO_AAat?8}%{;u&Xl+K)`luxpP`8B60kN3Qz!C5)Y?-sW64Y8FkS+&*Hke1*wya!j{ zeYzv}8uRUXw7Z`5IQY(@Uwx|NN>BAlu6iZ+t520&>8W1HRj=eS?-S9iV;EqvFxmLC_Wio`>z zY<&qq>Hg>H1%89yJ1ZBsegEo~>Q9)7dvZqgR)_R{`wAH|EUSd!Y83nGkJJocBg>ZR z1{(fB%|>kc)+^OPdUB;YL|qR9KY(JSKQZIG*gyOMp<7?UR1vISuk=*kt*7Mc#EkXF z%1;vO#JuVk8ghqxkzFTd?*;RozED=r1^W^6I~~#oP0WjEOE)oJC)xi^%q;OYTjav+ literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/baseline_get_example_dataframe.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/baseline_get_example_dataframe.pickledlist_sha256 new file mode 100644 index 0000000..b1aa353 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/baseline_get_example_dataframe.pickledlist_sha256 @@ -0,0 +1 @@ +59c8f38083406d03463b4151853627e4d0689f277697d82f9aa09a84670254de \ No newline at end of file From 5f62d5b226abf4414fd6a674ba9a3823d21e7e05 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 08:55:53 +0000 Subject: [PATCH 13/17] NPI-4460 add baseline for standalone sp3 comment validation --- tests/test_sp3.py | 69 +++++++++++++----- ..._comment_validation_standalone.pickledlist | Bin 0 -> 340 bytes ...t_validation_standalone.pickledlist_sha256 | 1 + 3 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 tests/unittest_baselines/TestSP3/test_sp3_comment_validation_standalone.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_sp3_comment_validation_standalone.pickledlist_sha256 diff --git a/tests/test_sp3.py b/tests/test_sp3.py index b1d05bd..9af63f8 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -843,6 +843,8 @@ def test_update_sp3_comments(self): def test_sp3_comment_validation_standalone(self): + objects_to_verify: list = [] + # Other examples of valid and invalid lines we could use. # valid_lines: list[str] = [ @@ -867,37 +869,47 @@ def test_sp3_comment_validation_standalone(self): # ] # Insufficient number of lines should fail validation - self.assertFalse(sp3.validate_sp3_comment_lines(["/* Must have >= 4 comment lines!"], STRICT_OFF)) + comment_lines = ["/* Must have >= 4 comment lines!"] + self.assertFalse(sp3.validate_sp3_comment_lines(comment_lines, STRICT_OFF)) + objects_to_verify.append(list(comment_lines)) + + comment_lines = [ + "/* Must have >= 4 comment lines!", + "/* Must have >= 4 comment lines!", + "/* Must have >= 4 comment lines!", + ] self.assertFalse( sp3.validate_sp3_comment_lines( - [ - "/* Must have >= 4 comment lines!", - "/* Must have >= 4 comment lines!", - "/* Must have >= 4 comment lines!", - ], + comment_lines, STRICT_OFF, ) ) + objects_to_verify.append(list(comment_lines)) + + comment_lines = [ + "/* Must have >= 4 comment lines!", + "/* Must have >= 4 comment lines!", + "/* Must have >= 4 comment lines!", + "/* Ok we're good now", + ] self.assertTrue( sp3.validate_sp3_comment_lines( - [ - "/* Must have >= 4 comment lines!", - "/* Must have >= 4 comment lines!", - "/* Must have >= 4 comment lines!", - "/* Ok we're good now", - ], + comment_lines, STRICT_OFF, ) ) + objects_to_verify.append(list(comment_lines)) # We have a convenience flag to turn that one off, to make testing less cumbersome: + comment_lines = ["/* Must have >= 4 comment lines! ...Unless that check is turned off"] self.assertTrue( sp3.validate_sp3_comment_lines( - ["/* Must have >= 4 comment lines! ...Unless that check is turned off"], + comment_lines, STRICT_OFF, skip_min_4_lines_test=True, ) ) + objects_to_verify.append(list(comment_lines)) # # The bulk tests may be overkill. # # Bulk test valid and invalid lines, with different settings @@ -929,46 +941,54 @@ def test_sp3_comment_validation_standalone(self): # ) # Uneventful cases - self.assertTrue( - sp3.validate_sp3_comment_lines(["/* this line is fine"], STRICT_RAISE, skip_min_4_lines_test=True) - ) + comment_lines = ["/* this line is fine"] + self.assertTrue(sp3.validate_sp3_comment_lines(comment_lines, STRICT_RAISE, skip_min_4_lines_test=True)) + objects_to_verify.append(list(comment_lines)) + + comment_lines = ["/* line 1", "/* line 2"] self.assertTrue( sp3.validate_sp3_comment_lines( - ["/* line 1", "/* line 2"], + comment_lines, STRICT_OFF, skip_min_4_lines_test=True, attempt_fixes=False, fail_on_fixed_issues=True, ) ) + objects_to_verify.append(list(comment_lines)) # Turning off fail_on_fixed_issues should make no difference here. + comment_lines = ["/* line 1", "/* line 2"] self.assertTrue( sp3.validate_sp3_comment_lines( - ["/* line 1", "/* line 2"], + comment_lines, STRICT_OFF, skip_min_4_lines_test=True, attempt_fixes=False, fail_on_fixed_issues=False, ) ) + objects_to_verify.append(list(comment_lines)) # Strict mode shouldn't change how valid lines are handled + comment_lines = ["/* line 1", "/* line 2"] self.assertTrue( sp3.validate_sp3_comment_lines( - ["/* line 1", "/* line 2"], + comment_lines, STRICT_RAISE, skip_min_4_lines_test=True, attempt_fixes=False, fail_on_fixed_issues=False, ) ) + objects_to_verify.append(list(comment_lines)) # With strictness off, invalid lines shouldn't raise exceptions, but should still fail validation # Note that fail-on-fixed currently has no effect if attempt_fixes is off. + comment_lines = ["this line has no lead-in"] self.assertFalse( sp3.validate_sp3_comment_lines( - ["this line has no lead-in"], + comment_lines, STRICT_OFF, skip_min_4_lines_test=True, attempt_fixes=False, @@ -976,6 +996,8 @@ def test_sp3_comment_validation_standalone(self): ), "Invalid comment line should fail validation but not raise exception as strict mode is off", ) + self.assertEqual(comment_lines, ["this line has no lead-in"], "No fix should be made when attempt_fixes=False") + # No need to add this one to the baseline, we have a full coverage assert here. with self.assertRaises(ValueError): sp3.validate_sp3_comment_lines( @@ -1017,6 +1039,7 @@ def test_sp3_comment_validation_standalone(self): ["/* this line has missing space after lead-in"], "Missing space should be addressed in place", ) + objects_to_verify.append(list(comment_lines)) # With fail on fixed: fail validation because the input was wrong, even though we were able to remedy it. comment_lines = ["/*this line has missing space after lead-in"] @@ -1035,6 +1058,7 @@ def test_sp3_comment_validation_standalone(self): ["/* this line has missing space after lead-in"], "Missing space should be addressed in place", ) + objects_to_verify.append(list(comment_lines)) # Same as above, but with strict mode: raise, that should be an exception. comment_lines = ["/*this line has missing space after lead-in"] @@ -1068,6 +1092,11 @@ def test_sp3_comment_validation_standalone(self): attempt_fixes=True, ) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") + def test_sp3_comment_reflow(self): # Test that string reflow utility correctly splits a string and converts it into SP3 comment lines. comment_string_to_reflow = """SP3 comment reflow test. This should not break words if possible. \ diff --git a/tests/unittest_baselines/TestSP3/test_sp3_comment_validation_standalone.pickledlist b/tests/unittest_baselines/TestSP3/test_sp3_comment_validation_standalone.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..f38b8b67a7e81688d2d3b04c36e2bb898ed4f0c0 GIT binary patch literal 340 zcmb7;zY+m45XLbYL+JrN6=OKVXlYamqf#m|cYBg?mzYb=wH_d4yssn$I%Bukf8XwJ zAKmA^-HLX0le4o}OmV2S0cUr@c7ruiRhA?T3L#049P>in2~O|K4*lBkiiXA&;i6Oq zsT$`6cK7Qbk|a6Fg0zO=%pm2IULkx{SCTTQJP*u>Fx>aUA7P&R7{9SS&xqZn`{b*D opYT2(d__C+($5vnG)kehkQeA1O9?DDRG}c2&4rBIKQ}0O0YDjX7XSbN literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_sp3_comment_validation_standalone.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_sp3_comment_validation_standalone.pickledlist_sha256 new file mode 100644 index 0000000..852d5d6 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_sp3_comment_validation_standalone.pickledlist_sha256 @@ -0,0 +1 @@ +a9e8c4308b4311a016f64b3ce8135e265331ac94c1fa2d80dc519350b09ad71a \ No newline at end of file From 06e49d52c79d9cc5b26a408e20073c805ceaeb9e Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 08:57:51 +0000 Subject: [PATCH 14/17] NPI-4460 add baseline for unsupported SP3 velicity handling --- tests/test_sp3.py | 16 ++++++++++++++++ ...tent_velocity_exception_handling.pickledlist | Bin 0 -> 3884 bytes ...locity_exception_handling.pickledlist_sha256 | 1 + 3 files changed, 17 insertions(+) create mode 100644 tests/unittest_baselines/TestSP3/test_gen_sp3_content_velocity_exception_handling.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_gen_sp3_content_velocity_exception_handling.pickledlist_sha256 diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 9af63f8..08c45e7 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -1169,24 +1169,40 @@ def test_gen_sp3_content_velocity_exception_handling(self): """ gen_sp3_content() can't yet output velocity data. Ensure raises by default, and removes vel columns with warning """ + + objects_to_verify: list = [] # Input data passed as bytes here, rather than using a mock file, because the mock file setup seems to break # part of Pandas Styler, which is used by gen_sp3_content(). Specifically, some part of Styler's attempt to # load style config files leads to a crash, despite some style config files appearing to read successfully) input_data_fresh = input_data + b"" # Lazy attempt at not passing a reference sp3_df = sp3.read_sp3(bytes(input_data_fresh), pOnly=False) + objects_to_verify.append(sp3_df) with self.assertRaises(NotImplementedError): generated_sp3_content = sp3.gen_sp3_content(sp3_df, continue_on_unhandled_velocity_data=False) + objects_to_verify.append(generated_sp3_content) with self.assertWarns(Warning) as warning_accessor: generated_sp3_content = sp3.gen_sp3_content(sp3_df, continue_on_unhandled_velocity_data=True) self.assertTrue("VX" not in generated_sp3_content, "Velocity data should be removed before outputting SP3") + objects_to_verify.append(generated_sp3_content) captured_warnings = warning_accessor.warnings self.assertEqual( "SP3 velocity output not currently supported! Dropping velocity columns before writing out.", str(captured_warnings[0].message), ) + self.assertEqual( + len(captured_warnings), + 1, + "Expected only 1 warning. Check what other warnings are being raised! Full list below:\n" + + stringify_warnings(captured_warnings), + ) + + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") def test_sp3_clock_nodata_to_nan(self): sp3_df = pd.DataFrame({("EST", "CLK"): [999999.999999, 123456.789, 999999.999999, 987654.321]}) diff --git a/tests/unittest_baselines/TestSP3/test_gen_sp3_content_velocity_exception_handling.pickledlist b/tests/unittest_baselines/TestSP3/test_gen_sp3_content_velocity_exception_handling.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..e038f8e72c615fa40abdc63b843ed3c6db1d0e05 GIT binary patch literal 3884 zcmdT`eQX=$8TUE1sf$P|+6qe&YeEtZTILE#uuH!q)cB3X+bvq~5oLk4v z&UU*_t=b}5T6h6B(65597AbZ}s zx=F|f-Ts)wYx~~2^YcE>@BKX=?{mQO_=Yx@J+|Qv^yLY?G^$sak+Nkl+bw@<&m8!=NV68bknsJ zTCtF?FxQu0)TY%+6NQnTMMKMzHMVf6Ha@Y3(QGv1wPLlPTb8~D)M}P7QZ7}hR&At8 zB)zsIM$LfU7Ma9J*C8CE^7uiV!5v8|=}-C&V{s^;k*2-Bnp*qB$@;&4b9dsmXJ3}s zA4OxQ{FC(yduH!=`TRNQ)%!l)c!;`K@7x%?<4Ye*ODMTz=Jj(I>qk2_mY=bvr42tj z{=&gOOx5?EIWuzjwb!Mc_y6ST<|EV6zLu}7|6$*0$@A0pdoR8=E$NRubLAKPXQlfG zzk8we`Dtn5Z>MhmWW#yM@z+QD8}qRbKb>v+`lmPizOn9$GGp)r^5<&>h=3AAmA6tX zj1U(lUEo%VcrfWES97)5X09WiH`^EbeZAw!bvGaAy$#Jszbkc4p5h0V+OZ4uM<2UsZ2jq{>nERj@|{G}&+Dm~+~qd@ z+4^7K3x4&ZcTU&+S2kJQ?3ud${>QtGO~0=H{-1wd({}n<>3gT9zCGQ2S^8RVYxv5^ zW75Hvx6VBM$4k=jx6fUD<%1LT{a3pt`u_2zWW7B*yZ-?udZY6f#be9iB%U!E>$3!A|#wbR9DA`7BmjN}tRkiYks^+}+4aNr47_4;mf+q?ds9_tC zIqZNjkawg!YB(ESl$q^-2OM1B=Blt6S)vBV+DSOMVKin6h8;>v>iogcSbIzBDi`_r zn*Cn#qufm%vcfUqS~ovaDuz`6-jOfs+%y^s(`PoLb=g8`tZ00ub%4&JV;c$Y z8*$GZT^?c|8iVYhmQZ@t1>w0uuB^Gvw>uhxt;SR}6W6jskasQdXfCR4$;zPC5BG8| zk&@wcZ(=~!azkmj2k$g|g#3yor`1?Ltn{myo@)}#=|PR;F*H7aET(Qza#}VA%7{{; z(6QA#C}*+>RapRgIWH5;;AK17NW{TdE=?Mut6Q3y-IU+uS=H+GE%13GA9Ub+Q9RAD zLqV~i1~FO>Q5>S_0L}3<%{pHO0^1%SB0x~U?h~YbCHk^l07&ZTS0I+;tO^8EDZ*MB zNTPTQ=YyXVHe!28e@`_E%$x_`XuSIWva=6jm}R&C!#RDi4kraR&m*7R#FENlC&4bg zSR;RYg8a}pYfyLL;ZA5@);NbxO|MnRdGs9eT}ut~mn4rgyRz%%^Tdv}y{M0otU@>I z#bSAPMH{V6KqsVE4Xvo>$-#1@vO|D|RjEP06jM_vS;=MX6Rqo>yJ(u0w`A0`998IS zIvSH{xX(m$5MEh229g<>?xf?CEbjbY2S)c2+u5{0lMDf^ z3b883>7F54iKb;WOZP~VWR0Fg@zdGij@@_4mdJn19>L&s)>;c*LH|N%|5&%1k;g9Ex)w`4itd4 zQs}m(A(d&mGsuCD!z?F?LAsL@!hDEf11v8>wDUY0h%j7;3vw(Su?OQ!HmJD^(FO7x z9|?pQo)`GAy*dyG!xI}6MGy`0oXChFHWc7i*0l&X2g}*(Dc8U)1jPu$f+@($PAC^9T3h!L>G^FTYp1;c^xjqwexi!dU1E*K!V1@I&riG&5H UR~#n@91{tJ0-$+C4zR)e2b^Q%4FCWD literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_gen_sp3_content_velocity_exception_handling.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_gen_sp3_content_velocity_exception_handling.pickledlist_sha256 new file mode 100644 index 0000000..62754d6 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_gen_sp3_content_velocity_exception_handling.pickledlist_sha256 @@ -0,0 +1 @@ +631133b8067e6255655b3f0e9238925af0c532851f3eae3c7688ef7c34717fa0 \ No newline at end of file From d1b1740a6a14ebf599c17885cb7868bdf58404a7 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:06:03 +0000 Subject: [PATCH 15/17] NPI-4460 add baseline for SP3 velocity interpolation. Note this is NOT a test against known correct values. --- tests/test_sp3.py | 19 +++++++++++++++++- .../TestSP3/test_velinterpolation.pickledlist | Bin 0 -> 5787 bytes .../test_velinterpolation.pickledlist_sha256 | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/unittest_baselines/TestSP3/test_velinterpolation.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_velinterpolation.pickledlist_sha256 diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 08c45e7..7dd5984 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -1210,6 +1210,8 @@ def test_sp3_clock_nodata_to_nan(self): expected_result = pd.DataFrame({("EST", "CLK"): [np.nan, 123456.789, np.nan, 987654.321]}) self.assertTrue(sp3_df.equals(expected_result)) + # Note while this does not test a full dataframe, it does use DF.equals(), so we are not adding baselining. + def test_sp3_pos_nodata_to_nan(self): """ This test data represents four 'rows' of data, each with an X, Y and Z component of the Position vector. @@ -1241,11 +1243,26 @@ def test_velinterpolation(self): is to check if the function runs without errors TODO: update that to check actual expected values """ + + # TODO note we do not currntly check for a confirmed correct answer. We just check that the answer has + # not changed from our baseline. + objects_to_verify: list = [] + result = sp3.read_sp3(input_data, pOnly=True, strict_mode=STRICT_OFF) + objects_to_verify.append(result) + r = sp3.getVelSpline(result) - r2 = sp3.getVelPoly(result, 2) self.assertIsNotNone(r) + objects_to_verify.append(r) + + r2 = sp3.getVelPoly(result, 2) self.assertIsNotNone(r2) + objects_to_verify.append(r2) + + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") def test_sp3_offline_sat_removal_standalone(self): """ diff --git a/tests/unittest_baselines/TestSP3/test_velinterpolation.pickledlist b/tests/unittest_baselines/TestSP3/test_velinterpolation.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..cae84445b85cd41e67c172bd83721742890fb589 GIT binary patch literal 5787 zcmeHK4{Q_X75~ov;)FmbYgw5xxU!Olbr{F6Zlg?`V_y<;$(`kMqNNhLos&yG8^@0P zCp2pjfeA{wLu$#_PMWHUs;bmYZP^N?Q(Nj#C}o3cnbve&H%+uF+L%x`ZDXXggT3!A z=QzaK0xgp!asKmtclW;c-o5X=-}~K=h+uY)l|2d^>n2(nx`kD_Gv_~ z45~S`$(WCjmD|ZykaH+~y&0k}EftTaa#|*(CbOPCHKq1ynJm$(8;RRe*(&p*l@u6=M~3&Z@&NH=$SjWlgC0ooSAIOYzw>|gR;qAXXHG2O`FLmu2 zd&@A3zXTR+bGNa1$I~Bg+&#{1t-PgnZ}U$%#}9A5`{dX-r|vm$_E5_aZb!$rPgK1; z&h>w|f9bz&J8s#q3HG_$Fq3Oyn>9ZC_e+;+ZokA?=hkRlr{RO=unUfBw_)j=ZJe~> z5S-WDhEs4|cN;FD;=0?Y5ZrUKu|&5?)5_}pE6QpEEh`E(3`_96)<-k9-7Qqk%}kD} zuolyCQbDCq_30E;3G+Ukf_Xx9se&A#&wA^0r0mx2)v}(Lngx8VQc~I$C6t0|6qJ^f zlQggoMB69^TA~z)T}f>~OJ=DJybnLFJ478(drDEDRUrDj07Y(vAJ9@c@YXyFXS8mjyP*ka z`2h{OMwOI_#dC^zo%t5gnNIlKV_3!qO{1Fb>`HfQ=7Y^Z$!f3*c@}S&f^VEJ2j0~b z0c>zEa2QozR@c~YpxWuGK=jAh><%ia>6O`ZCYR21gU=!~7>J1BjwotYT%M$HdCH9a zY%!w2roNWc1pZ<m`=$+3eY-_N3X{0GrzE z^-YFWw}&I5sX)UgsM|B5;eyo=4HrTTW3!cySW$*S@G)YA-xcw1sls!3!iQ~xj9@M&F&PPLSJX~%Hwem`|GYoUs9czztx2W)8DbNBd-BP4+QlcWHJ^otPs}%# zPgY8z2F_?@DF%kCiJRb@npJv|YA-D{{qt%vnck9By7T=&P1T&HB-I%0BD=GRVUQN{ zKwtw>TN^J%WutwqTm5C0W%>4q6z2URD~J67o`reDABCKg*#JmJcy<*V^heqtJA=`$ zu+4m=%`XD9uZP8}uJs`L-PE=mUc=I|0j(mn$_LrTPFD1Xc}Zp){VgIJm84ddtq<~1 zup1H$$5xug!-0-v$>J(BOxs5+#v`fn_^I77}k=AT6@c&FDxp z-O;b+FwqgoC%$I{GO-7S-PwE$+s&6DK%&ZWRNig8Z`2Mq4#9$}eTleQ_J6fZ-YojL zv+^tD|KHAyPDV@Tzp_||v28QxWhvF1ZC>QC(+FU>+^|pPM&O|V?yH~p&N#WfYnQI* zIrhiGBOe7WHXU_x7oVLNiJkvT;pp>`iF=-Ka?g&$x3B)(KML#b_|sVXJ8u`{Epp4? zq2CqW|H8|XwC!|ZXy1oBcdY!Nuzb1g#q8cQh3L)$y(cc5Eu3GnU}&5Dy$z2ZTr>96 zEpG0;-<z9f@9D3e^t1!>>G7X&1t>jRSPwxB=&(xf;pFt z>aZFj{xSXueqMKU@U8J_ZK(*vGy6r6QR2h+Rb0TM_y~RtAH~Ov*jnUp7V=VKwzV3X zoEGV`+wJ99j3r#k`(R$SwUzBH_yc?ppSyyN{SB^77}sX&D@FG7nJ_E^`x6TQVGP_) zETl6N@o(T2n!>jmcvXDUo2!LZ>|jULh`E;1xQ(@esu;gnr#a@d^!X-A3^Wq26Cmyh7-m r^AxWToBs~QD}-tcyh1JCU2$FTDsco#s1$8(pw(ygK6n*qrFs4h9hm^p literal 0 HcmV?d00001 diff --git a/tests/unittest_baselines/TestSP3/test_velinterpolation.pickledlist_sha256 b/tests/unittest_baselines/TestSP3/test_velinterpolation.pickledlist_sha256 new file mode 100644 index 0000000..18e9109 --- /dev/null +++ b/tests/unittest_baselines/TestSP3/test_velinterpolation.pickledlist_sha256 @@ -0,0 +1 @@ +22c1554ab1b7eebdf60995e9eefd2eb5b7f582061d3f1e7813fb0f6b2b5085f9 \ No newline at end of file From 2c8d31b2aa478ab968ac25fa5b50e1e07f995bc6 Mon Sep 17 00:00:00 2001 From: Nathan <95725385+treefern@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:09:47 +0000 Subject: [PATCH 16/17] NPI-4460 add baseline for SP3 offline sat removal --- tests/test_sp3.py | 11 +++++++++++ ...p3_offline_sat_removal_standalone.pickledlist | Bin 0 -> 5492 bytes ...ine_sat_removal_standalone.pickledlist_sha256 | 1 + 3 files changed, 12 insertions(+) create mode 100644 tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal_standalone.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal_standalone.pickledlist_sha256 diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 7dd5984..173b503 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -1269,6 +1269,9 @@ def test_sp3_offline_sat_removal_standalone(self): Standalone test for remove_offline_sats() using manually constructed DataFrame to avoid dependency on read_sp3() """ + + objects_to_verify: list = [] + sp3_df_nans = TestSP3.get_example_dataframe("offline_sat_nan") sp3_df_zeros = TestSP3.get_example_dataframe("offline_sat_zero") @@ -1282,6 +1285,7 @@ def test_sp3_offline_sat_removal_standalone(self): ["G01", "G02", "G03"], "Should start with 3 SVs", ) + objects_to_verify.extend([sp3_df_nans, sp3_df_zeros]) sp3_df_zeros_removed = sp3.remove_offline_sats(sp3_df_zeros) sp3_df_nans_removed = sp3.remove_offline_sats(sp3_df_nans) @@ -1297,6 +1301,13 @@ def test_sp3_offline_sat_removal_standalone(self): "Should be two SVs after removing offline ones", ) + objects_to_verify.extend([sp3_df_zeros_removed, sp3_df_nans_removed]) + + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") + def test_sp3_offline_sat_removal(self): sp3_df = sp3.read_sp3(offline_sat_test_data, pOnly=False, strict_mode=STRICT_OFF) diff --git a/tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal_standalone.pickledlist b/tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal_standalone.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..5468dc7c70d06cad9f06f4955eadfdeb4ffe099d GIT binary patch literal 5492 zcmd^C*>4*~829>$-8M}R3RD7A2_c0;CTT0g14muQNmDoL$aV`YRI9Z;&FAfU9qrmF zks7t7>Je!lkUB`EMS*gNCnWj?;tH4e2N2=`2?*yqFNhcTzFm)xwVhh6h$`VIGdnZi z%zVf1`+d8ogD;o1`JCTTK5TEERgI!*#R?UZ#g3V3nQ=QhrdHK)=lK$U@C;u-xu8;> zF}ZzTt9Z$%GSg6XD^^wwb%vQ1w>Rw7D~01z?wDWV_MP<|ieAcFvBnl|4=FWcwp2K- zGbK-Z9ATqYo}G^=jyC0*UM;Dnsm|lEipdHU!>XFKLX{*#jwH7SPNTg@r0D; z7kGvbr=(OM6+O>klN~Bn=Vuvh@*jJgFJ5%l1Ge-8mt}eXKlpQ+l&{M3i;3?(*lsL8 zw{(3^uikauU#CRxl@s&vS1)YMeDm#cuU^QB-YX~OLw}DgJsuiqc7Tp1lz(aPZbum~(=y4c=Efh_-L&Q?T!FJo9Zj0YZPyC*y z_#NeTAeqf^m8=aK1ZcT-9<9R5cH{lwf}+q}gCmQHfKT$10JEc3#jIA$A~U&+U`I2Y%Vn7f zV;&`AMqMxVGR$Asti^3`Z#T_0?OA>}MnH+zdq>TL9ooNhWMqVkJA2*JbYNg&WE3MX zF|yNXch%i!2c|Q!dkljNDc#}4DC&wXh`Mlx5N9A@v$WC+0qJUiKy&cZF4}uUO3nTPU_F^7*5-1?1>3%wxSkA#*zq)f`=={EeWg58Y`?E2#gv^0@UXU3H5;VkiZ zJQr6EW|Meq1fP?+$*Ckxk53**D!IecNe-vrG@P*`vXY!mC-&iKs8b?+P|hjY93Df*(NXR#1Ba5C>||PQQ@B@b1=G!{j|MAd zeg~C}`ZzEVk0YS7)3|HUImMc<%fbrsC;ky%kRDy*{;pt>W+dMiQ)}%j!um*Tba!kN zVULwvMPldhjvaQ&FwqQ6hv!tiMhoBD5iI=*mS`N@kCtw9Yeh}1S;S?R?QlIw;0h&_ z;&84CuhKorFkY=xu(HcXIrlF5qu1vz^AS=U>QDx&*2OB6259ZtCwE{I|~G_<{Tk=%S;YGz>lpg4l8TN;o3a1 zQb+!1FSg9CwXcvWgAQX3Yhmm)i<$v!917Rg(eNrY>#^y|-P#bXT+udBhiGhP;9DT( zFn)R=F~8&`+8d3%gnFZ~mpE@UCPLMAptUWZI@&(6zFXbaWLimhnMHfXonD&0E;m(oXtlUGJ<6I+I2R$!Z|I1{B z2U;=}plh0f(V3%g66bFF1cjUVFcA5+#;FNE!3z8gzre5X8~hG`^pl0Rk$v9$Qz_Rx M`M2<2 Date: Tue, 3 Mar 2026 04:05:01 +0000 Subject: [PATCH 17/17] NPI-4460 add baseline for SP3 offline sat removal and fix function to avoid modifying passed reference before returning --- gnssanalysis/gn_io/sp3.py | 6 ++--- tests/test_sp3.py | 25 +++++++++++++++--- .../test_sp3_offline_sat_removal.pickledlist | Bin 0 -> 5313 bytes ...sp3_offline_sat_removal.pickledlist_sha256 | 1 + 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal.pickledlist create mode 100644 tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal.pickledlist_sha256 diff --git a/gnssanalysis/gn_io/sp3.py b/gnssanalysis/gn_io/sp3.py index 2938e59..e1c9cab 100644 --- a/gnssanalysis/gn_io/sp3.py +++ b/gnssanalysis/gn_io/sp3.py @@ -407,17 +407,17 @@ def remove_offline_sats(sp3_df: _pd.DataFrame, df_friendly_name: str = "") -> _p offline_sats = sp3_df[mask_either].index.get_level_values(1).unique() # Using that list of offline / partially offline sats, remove all entries for those sats from the SP3 DataFrame: - sp3_df = sp3_df.drop(offline_sats, level=1, errors="ignore") + sp3_df_cleaned = sp3_df.drop(offline_sats, level=1, errors="ignore") if len(offline_sats) > 0: # Update the internal representation of the SP3 header to match the change - remove_svs_from_header(sp3_df, offline_sats.values) + remove_svs_from_header(sp3_df_cleaned, offline_sats.values) logger.info( f"Dropped offline / nodata sats from {df_friendly_name} SP3 DataFrame (including header): {offline_sats.values}" ) else: logger.info(f"No offline / nodata sats detected to be dropped from {df_friendly_name} SP3 DataFrame") - return sp3_df + return sp3_df_cleaned def filter_by_svs( diff --git a/tests/test_sp3.py b/tests/test_sp3.py index 173b503..2e4e6be 100644 --- a/tests/test_sp3.py +++ b/tests/test_sp3.py @@ -4,6 +4,7 @@ import numpy as np import pandas as pd +from pandas import DataFrame from gnssanalysis.filenames import convert_nominal_span, determine_properties_from_filename import gnssanalysis.gn_io.sp3 as sp3 @@ -1309,7 +1310,10 @@ def test_sp3_offline_sat_removal_standalone(self): self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") def test_sp3_offline_sat_removal(self): + objects_to_verify: list = [] + sp3_df = sp3.read_sp3(offline_sat_test_data, pOnly=False, strict_mode=STRICT_OFF) + objects_to_verify.append(sp3_df) # Confirm starting state of content self.assertEqual( @@ -1328,26 +1332,39 @@ def test_sp3_offline_sat_removal(self): sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED, "3", "Header should have 2 SVs before removing offline" ) + df_snapshot = DataFrame(sp3_df) # Now make the changes - this should also update the header - sp3_df = sp3.remove_offline_sats(sp3_df) + sp3_df_cleaned = sp3.remove_offline_sats(sp3_df) + objects_to_verify.append(sp3_df_cleaned) + # Ensure the source DF did NOT get modified... + df_snapshot_after = DataFrame(sp3_df) + self.assertTrue( + df_snapshot.equals(df_snapshot_after), + "Original DF should not be modified by function that returns a new copy", + ) # Check contents self.assertEqual( - sp3_df.index.get_level_values(1).unique().array.tolist(), + sp3_df_cleaned.index.get_level_values(1).unique().array.tolist(), ["G02", "G03"], "Should be two SVs after removing offline ones", ) # Check header self.assertEqual( - sp3_df.attrs["HEADER"].SV_INFO.index.array.tolist(), + sp3_df_cleaned.attrs["HEADER"].SV_INFO.index.array.tolist(), ["G02", "G03"], "Should be two SVs in parsed header after removing offline ones", ) self.assertEqual( - sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED, "2", "Header should have 2 SVs after removing offline" + sp3_df_cleaned.attrs["HEADER"].HEAD.SV_COUNT_STATED, "2", "Header should have 2 SVs after removing offline" ) + # UnitTestBaseliner.mode = "baseline" + # UnitTestBaseliner.create_baseline(objects_to_verify) # DO NOT commit this line un-commented. + + self.assertTrue(UnitTestBaseliner.verify(objects_to_verify), "Hash verification should pass") + # sp3_test_data_truncated_cod_final is input_data2 def test_filter_by_svs(self): sp3_df = sp3.read_sp3(input_data2, pOnly=False) diff --git a/tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal.pickledlist b/tests/unittest_baselines/TestSP3/test_sp3_offline_sat_removal.pickledlist new file mode 100644 index 0000000000000000000000000000000000000000..cf86ef77c720c980658af5897e4bc5d8cc559e8d GIT binary patch literal 5313 zcmcgveQX>@6~FHf=kie#nzkfil@#O_bUr(kNkgUDIiH>D#&?$Qk|rf)v*#OoceCEJ zZg-tp)O7-N8dobUl1W6R(ndlN1&Kc@xl!N`di?`ZLV;2U6k4hxklIRBkO&GX0?K={ zx3+K37cL>NXV1>Oee-*7-pss@BY{)r)_d49Oxvv6CPb|$>hY0MnZ!rS;y9sJv{y95 zKGr`;?>S1RfiED8kCmylc{#hR8KkU1(ncw(+Y{w6m=oH|rNWqk085-CosH^e_>cM+ZJ5+=BVJpR?wNfjp zFRiatej})J<-NXrk9&Snd2sXJFK_?!tCc^0{@8_=x?byk@()`JqgO9h-ktdF)y>C# zx~R^}|2z^t{Vvd7x!6Aa=Fb-CsE)K1B>S3|{me_z+2@bGQ2FayJ0Cv3>Gkfb2ZtWJ z{NoGV=P!ONGPC)`O5VEixlf+{MYm&$UwwU@{_z*iSKey<-aEexF43#9eZ0SU*$>Pf z`1ZG6)GISDYj6De+?Ttroqhc6g%d}+fAHL0?cV@;@zm?veh+jzS}i+ba7-md`tAI z>>wYyQQ3`TIWnB@ZrMxps_YOSzERl@=FPvWvcr6&IoWqwzHE(Kc+BM%9z?f*&eF?K z>CxIBu`gAGk2WXFK%iO(993bWe9NtXY2jDh3Yb-VY&ncU(FS+aMt~xDl<4t-s6*Un z5j1jGNNYetXM5*LoXn>K>j3(8hZO@%NF|d*)lr6YMtar?F8>%PLPVnVPSOt^lUdaA zr*e7luzkCG`g7Fs=kmQ2ZE6KH3~3qj7`oXr((@on`c6GiWCKm0$!BufjnP+|o~N=0HXtj^2FKrWm= zxnn6TN#;sHDdvI42agvXcEVT0dTlNl3x>iT{P+6&0puiBR47-1;I59+aN{8VXiiQEn5ZKfT?Mn6ZreIR* z^4p-3vJ87*g>rVsNwcJo6t-G(vNom?S0V*@Slcij;bykVw|JZbC?B$<++HD_>C4s} z&FkEUHH!ego+KM z%Zg-#)L=Hb3+6|%Lp!T{;laHE-dM;5LBC{nPbM$q@&Jj@2y06W?@bNm(%DRn@{M*J zHHRHvgXgdMG`ukm@9GwR#OIBA0VcaDaob{xtfkbbqch0mj<3djMJ(!FTNS59X-y1+T{g2}^+Y zqZ>o%EA70p71$h4v$&l~d>QrsZKr8C)rok=een*vZV(|pP-PQnWQACokFJN^j7d>7 z@!KB!7J>3Y0H3QvQfo*1y&RXx=2JVk zyd>)!{E3_-k4fqjH(n~rqcSOSqq0i4(NdW+hz@!E7+2ENsW`Q|aB|w<4vRWhD$8Rs zR7NlloUS~H)5@iBPSiNqXKtcgDnc3aS*~D0bh!s7>Qc#6i=0+6xB}q}@et8C01{0@ z0x&}~03=R{6_^kWiCP|NDi&L|@>O^p*Uf_Dw@WqfA zmTr6Ot{#@E28KM)JhFr6e!C@d-3ID-i_?0-)$p{%_BFT*E5|D{U_wQgqo$& zU%I(zvo|+w{^q7VZBJu)we<)p%9Q>Bv=14rk#i5(K`?|_Potb?k;m(S z9~zr9NO+k0Rn~%^VXjxX4STlJZBy3cU;{Lc_fbA!4;p1F|3%DNZ7*xJeUDh%C>eHG zYeIX?BCi|SaXLF$%067*erz7W2II1F0GnZKzKD&0%|UELXe@6h#mN0Xirdb@ZmkWb zsWJ_^LtIv7a3Od3pg96P7OR!7Agv2&Pe6}F=+*TRS~-qv7@d_lZ0sXa8>gJ`tIPuj b5akqhPeW4|