You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					150 lines
				
				4.5 KiB
			
		
		
			
		
	
	
					150 lines
				
				4.5 KiB
			| 
								 
											2 years ago
										 
									 | 
							
								#!/usr/bin/env python3
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import unittest
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from parameterized import parameterized
							 | 
						||
| 
								 | 
							
								from typing import Optional, Union, List
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from openpilot.selfdrive.test.openpilotci import get_url, upload_file
							 | 
						||
| 
								 | 
							
								from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_process_diff
							 | 
						||
| 
								 | 
							
								from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process
							 | 
						||
| 
								 | 
							
								from openpilot.system.version import get_commit
							 | 
						||
| 
								 | 
							
								from openpilot.tools.lib.filereader import FileReader
							 | 
						||
| 
								 | 
							
								from openpilot.tools.lib.helpers import save_log
							 | 
						||
| 
								 | 
							
								from openpilot.tools.lib.logreader import LogReader, LogIterable
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
							 | 
						||
| 
								 | 
							
								REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
							 | 
						||
| 
								 | 
							
								EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_log_data(segment):
							 | 
						||
| 
								 | 
							
								  r, n = segment.rsplit("--", 1)
							 | 
						||
| 
								 | 
							
								  with FileReader(get_url(r, n)) as f:
							 | 
						||
| 
								 | 
							
								    return f.read()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								ALL_PROCS = sorted({cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS})
							 | 
						||
| 
								 | 
							
								PROC_TO_CFG = {cfg.proc_name: cfg for cfg in CONFIGS}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								cpu_count = os.cpu_count() or 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestProcessReplayBase(unittest.TestCase):
							 | 
						||
| 
								 | 
							
								  """
							 | 
						||
| 
								 | 
							
								  Base class that replays all processes within test_proceses from a segment,
							 | 
						||
| 
								 | 
							
								  and puts the log messages in self.log_msgs for analysis by other tests.
							 | 
						||
| 
								 | 
							
								  """
							 | 
						||
| 
								 | 
							
								  segment: Optional[Union[str, LogIterable]] = None
							 | 
						||
| 
								 | 
							
								  tested_procs: List[str] = ALL_PROCS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  @classmethod
							 | 
						||
| 
								 | 
							
								  def setUpClass(cls, create_logs=True):
							 | 
						||
| 
								 | 
							
								    if "Base" in cls.__name__:
							 | 
						||
| 
								 | 
							
								      raise unittest.SkipTest("skipping base class")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if isinstance(cls.segment, str):
							 | 
						||
| 
								 | 
							
								      cls.log_reader = LogReader.from_bytes(get_log_data(cls.segment))
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								      cls.log_reader = cls.segment
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if create_logs:
							 | 
						||
| 
								 | 
							
								      cls._create_log_msgs()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  @classmethod
							 | 
						||
| 
								 | 
							
								  def _run_replay(cls, cfg):
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								      return replay_process(cfg, cls.log_reader, disable_progress=True)
							 | 
						||
| 
								 | 
							
								    except Exception as e:
							 | 
						||
| 
								 | 
							
								      raise Exception(f"failed on segment: {cls.segment} \n{e}") from e
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  @classmethod
							 | 
						||
| 
								 | 
							
								  def _create_log_msgs(cls):
							 | 
						||
| 
								 | 
							
								    cls.log_msgs = {}
							 | 
						||
| 
								 | 
							
								    cls.proc_cfg = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for proc in cls.tested_procs:
							 | 
						||
| 
								 | 
							
								      cfg = PROC_TO_CFG[proc]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      log_msgs = cls._run_replay(cfg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      cls.log_msgs[proc] = log_msgs
							 | 
						||
| 
								 | 
							
								      cls.proc_cfg[proc] = cfg
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestProcessReplayDiffBase(TestProcessReplayBase):
							 | 
						||
| 
								 | 
							
								  """
							 | 
						||
| 
								 | 
							
								  Base class for checking for diff between process outputs.
							 | 
						||
| 
								 | 
							
								  """
							 | 
						||
| 
								 | 
							
								  update_refs = False
							 | 
						||
| 
								 | 
							
								  upload_only = False
							 | 
						||
| 
								 | 
							
								  long_diff = False
							 | 
						||
| 
								 | 
							
								  ignore_msgs: List[str] = []
							 | 
						||
| 
								 | 
							
								  ignore_fields: List[str] = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def setUp(self):
							 | 
						||
| 
								 | 
							
								    super().setUp()
							 | 
						||
| 
								 | 
							
								    if self.upload_only:
							 | 
						||
| 
								 | 
							
								      raise unittest.SkipTest("skipping test, uploading only")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  @classmethod
							 | 
						||
| 
								 | 
							
								  def setUpClass(cls):
							 | 
						||
| 
								 | 
							
								    super().setUpClass(not cls.upload_only)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if cls.long_diff:
							 | 
						||
| 
								 | 
							
								      cls.maxDiff = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cls.cur_commit = get_commit()
							 | 
						||
| 
								 | 
							
								    cls.assertNotEqual(cls.cur_commit, None, "Couldn't get current commit")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cls.upload = cls.update_refs or cls.upload_only
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								      with open(REF_COMMIT_FN) as f:
							 | 
						||
| 
								 | 
							
								        cls.ref_commit = f.read().strip()
							 | 
						||
| 
								 | 
							
								    except FileNotFoundError:
							 | 
						||
| 
								 | 
							
								      print("Couldn't find reference commit")
							 | 
						||
| 
								 | 
							
								      sys.exit(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cls._create_ref_log_msgs()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  @classmethod
							 | 
						||
| 
								 | 
							
								  def _create_ref_log_msgs(cls):
							 | 
						||
| 
								 | 
							
								    cls.ref_log_msgs = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for proc in cls.tested_procs:
							 | 
						||
| 
								 | 
							
								      cur_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.cur_commit}.bz2")
							 | 
						||
| 
								 | 
							
								      if cls.update_refs:  # reference logs will not exist if routes were just regenerated
							 | 
						||
| 
								 | 
							
								        ref_log_path = get_url(*cls.segment.rsplit("--", 1))
							 | 
						||
| 
								 | 
							
								      else:
							 | 
						||
| 
								 | 
							
								        ref_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.ref_commit}.bz2")
							 | 
						||
| 
								 | 
							
								        ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if not cls.upload_only:
							 | 
						||
| 
								 | 
							
								        save_log(cur_log_fn, cls.log_msgs[proc])
							 | 
						||
| 
								 | 
							
								        cls.ref_log_msgs[proc] = list(LogReader(ref_log_path))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if cls.upload:
							 | 
						||
| 
								 | 
							
								        assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}"
							 | 
						||
| 
								 | 
							
								        upload_file(cur_log_fn, os.path.basename(cur_log_fn))
							 | 
						||
| 
								 | 
							
								        os.remove(cur_log_fn)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  @parameterized.expand(ALL_PROCS)
							 | 
						||
| 
								 | 
							
								  def test_process_diff(self, proc):
							 | 
						||
| 
								 | 
							
								    if proc not in self.tested_procs:
							 | 
						||
| 
								 | 
							
								      raise unittest.SkipTest(f"{proc} was not requested to be tested")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cfg = self.proc_cfg[proc]
							 | 
						||
| 
								 | 
							
								    log_msgs = self.log_msgs[proc]
							 | 
						||
| 
								 | 
							
								    ref_log_msgs = self.ref_log_msgs[proc]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    diff = compare_logs(ref_log_msgs, log_msgs, self.ignore_fields + cfg.ignore, self.ignore_msgs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    diff_short, diff_long = format_process_diff(diff)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self.assertEqual(len(diff), 0, "\n" + diff_long if self.long_diff else diff_short)
							 |