1212import sys
1313import tempfile
1414import unittest
15+ import warnings
1516
1617REPO_ROOT = pathlib .Path (__file__ ).resolve ().parents [3 ]
1718sys .path .insert (0 , str (REPO_ROOT ))
1819
1920from scripts import lavafuncs # pylint: disable=wrong-import-position
2021from scripts .context import Context # pylint: disable=wrong-import-position
2122from leapp_functions .app .artifact_result import ArtifactResult # pylint: disable=wrong-import-position
22- from scripts .ilapfuncs import artifact_processor , OutputParameters # pylint: disable=wrong-import-position
23+ from scripts .ilapfuncs import artifact_processor , kmlgen , OutputParameters # pylint: disable=wrong-import-position
2324
2425UTC = datetime .timezone .utc
2526STAMP = datetime .datetime (2022 , 3 , 4 , 12 , 34 , 56 , tzinfo = UTC )
@@ -281,8 +282,10 @@ def test_list_module_that_raises_leaves_the_same_nothing(self):
281282 self .assertNotIn ('Probe Category' , lavafuncs .lava_data ['artifacts' ])
282283
283284class TestReplayKeepsPythonText (ArtifactResultTestCase ):
284- """Floats and booleans in untyped columns replay exactly as the list path prints them,
285- while the list path's own stored values are unchanged."""
285+ """Floats and booleans in untyped LAVA columns, and the coordinates kmlgen keeps in
286+ _latlong.db, are stored as the text Python prints for them. The stored text is then the
287+ same whatever SQLite library the run links, matches the HTML, TSV and KML output, and
288+ replays as the original values."""
286289
287290 def setUp (self ):
288291 super ().setUp ()
@@ -299,24 +302,53 @@ def test_streamed_rows_replay_as_the_original_values(self):
299302 replayed = list (lavafuncs .lava_iter_artifact_rows (
300303 result .table_name , headers , result .object_columns , result .row_count ))
301304 self .assertEqual (replayed , rows )
302- # The stored text is Python's own rendering, not SQLite's 15-digit one .
305+ # The stored text is Python's own rendering, not SQLite's, which depends on its version .
303306 stored = lavafuncs .lava_db .execute (
304307 f'SELECT latitude, flag FROM { lavafuncs .quote_sql_name (result .table_name )} ORDER BY rowid' ).fetchall ()
305308 self .assertEqual (stored , [('35.65956623101914' , 'True' ), ('-78.87282251025944' , 'False' )])
306309
307- def test_list_path_storage_is_unchanged (self ):
308- headers = ('Latitude' , 'Flag' , 'When' )
310+ def test_list_path_stores_python_text (self ):
311+ headers = ('Latitude' , 'Flag' , ( 'When' , 'datetime' ) )
309312 table , object_columns , column_map = lavafuncs .lava_process_artifact (
310- 'Unit Category' , 'unit_module' , 'List Artifact' , (headers [0 ], headers [1 ], (headers [2 ], 'datetime' )),
311- 1 , func_name = 'list_artifact' )
313+ 'Unit Category' , 'unit_module' , 'List Artifact' , headers , 2 , func_name = 'list_artifact' )
312314 lavafuncs .lava_insert_sqlite_data (
313- table , [(35.65956623101914 , True , '2024-07-21T15:16:47.357843+00:00' )],
314- object_columns , (headers [0 ], headers [1 ], (headers [2 ], 'datetime' )), column_map )
315+ table , [(35.65956623101914 , True , '2024-07-21T15:16:47.357843+00:00' ),
316+ (670162.147879839 , False , '2024-07-21T15:16:47.357843+00:00' )],
317+ object_columns , headers , column_map )
315318 stored = lavafuncs .lava_db .execute (
316- f'SELECT latitude, flag, "when" FROM { lavafuncs .quote_sql_name (table )} ' ).fetchone ()
317- # SQLite's rendering of a bound float and boolean into TEXT columns, and a whole-second
318- # int epoch for an ISO string: what the list path stored before this change.
319- self .assertEqual (stored , ('35.6595662310191' , '1' , 1721575007 ))
319+ f'SELECT latitude, flag, "when" FROM { lavafuncs .quote_sql_name (table )} ORDER BY rowid' ).fetchall ()
320+ # Bound as numbers, SQLite 3.51 and earlier store the first float as 35.6595662310191,
321+ # 3.52.0 and later store the second as 670162.14787983894, and both store a boolean as
322+ # 1 or 0. The datetime column still stores the whole-second epoch.
323+ self .assertEqual (stored , [('35.65956623101914' , 'True' , 1721575007 ),
324+ ('670162.147879839' , 'False' , 1721575007 )])
325+
326+ def test_streamed_dates_in_an_untyped_column_are_bound_as_text (self ):
327+ # As on the list path, sqlite3's default date adapters, deprecated from Python 3.12,
328+ # never see a value, and the stored text is what they wrote.
329+ with warnings .catch_warnings (record = True ) as caught :
330+ warnings .simplefilter ('always' )
331+ result = Context .create_artifact_result (headers = HEADERS )
332+ for row in ROWS :
333+ result .add_row (row )
334+ result .close ()
335+ self .assertEqual ([w for w in caught if issubclass (w .category , DeprecationWarning )
336+ and 'adapter' in str (w .message )], [])
337+ stored = [row [0 ] for row in lavafuncs .lava_db .execute (
338+ f'SELECT moment FROM { lavafuncs .quote_sql_name (result .table_name )} ORDER BY rowid' )]
339+ self .assertEqual (stored , MOMENT_TEXT )
340+
341+ def test_kml_sidecar_stores_python_text_for_coordinates (self ):
342+ report_folder = pathlib .Path (self .tmpdir , 'category' , 'artifact' )
343+ report_folder .mkdir (parents = True )
344+ kmlgen (str (report_folder ), 'Python Text' , [(STAMP , 35.65956623101914 , - 78.87282251025944 )],
345+ ['Timestamp' , 'Latitude' , 'Longitude' ])
346+ db = sqlite3 .connect (pathlib .Path (self .tmpdir , '_KML Exports' , '_latlong.db' ))
347+ stored = db .execute ('SELECT latitude, longitude FROM data' ).fetchall ()
348+ db .close ()
349+ # Bound as numbers these read 35.6595662310191 and -78.8728225102594 through
350+ # SQLite 3.51, and 35.659566231019142 and -78.872822510259439 from 3.52.0.
351+ self .assertEqual (stored , [('35.65956623101914' , '-78.87282251025944' )])
320352
321353 def test_python_text_restore_is_exact_and_leaves_other_text_alone (self ):
322354 restore = lavafuncs ._restore_lava_value # pylint: disable=protected-access
0 commit comments