1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li"""Base support for parser scenario testing. 3*9c5db199SXin Li""" 4*9c5db199SXin Li 5*9c5db199SXin Lifrom __future__ import absolute_import 6*9c5db199SXin Lifrom __future__ import division 7*9c5db199SXin Lifrom __future__ import print_function 8*9c5db199SXin Lifrom os import path 9*9c5db199SXin Liimport six.moves.configparser, os, shelve, shutil, sys, tarfile, time 10*9c5db199SXin Liimport difflib, itertools 11*9c5db199SXin Liimport common 12*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils, autotemp 13*9c5db199SXin Lifrom autotest_lib.tko import parser_lib 14*9c5db199SXin Lifrom autotest_lib.tko.parsers.test import templates 15*9c5db199SXin Lifrom autotest_lib.tko.parsers.test import unittest_hotfix 16*9c5db199SXin Liimport six 17*9c5db199SXin Lifrom six.moves import zip 18*9c5db199SXin Li 19*9c5db199SXin LiTEMPLATES_DIRPATH = templates.__path__[0] 20*9c5db199SXin Li# Set TZ used to UTC 21*9c5db199SXin Lios.environ['TZ'] = 'UTC' 22*9c5db199SXin Litime.tzset() 23*9c5db199SXin Li 24*9c5db199SXin LiKEYVAL = 'keyval' 25*9c5db199SXin LiSTATUS_VERSION = 'status_version' 26*9c5db199SXin LiPARSER_RESULT_STORE = 'parser_result.store' 27*9c5db199SXin LiRESULTS_DIR_TARBALL = 'results_dir.tgz' 28*9c5db199SXin LiCONFIG_FILENAME = 'scenario.cfg' 29*9c5db199SXin LiTEST = 'test' 30*9c5db199SXin LiPARSER_RESULT_TAG = 'parser_result_tag' 31*9c5db199SXin Li 32*9c5db199SXin Li 33*9c5db199SXin Liclass Error(Exception): 34*9c5db199SXin Li pass 35*9c5db199SXin Li 36*9c5db199SXin Li 37*9c5db199SXin Liclass BadResultsDirectoryError(Error): 38*9c5db199SXin Li pass 39*9c5db199SXin Li 40*9c5db199SXin Li 41*9c5db199SXin Liclass UnsupportedParserResultError(Error): 42*9c5db199SXin Li pass 43*9c5db199SXin Li 44*9c5db199SXin Li 45*9c5db199SXin Liclass UnsupportedTemplateTypeError(Error): 46*9c5db199SXin Li pass 47*9c5db199SXin Li 48*9c5db199SXin Li 49*9c5db199SXin Li 50*9c5db199SXin Liclass ParserException(object): 51*9c5db199SXin Li """Abstract representation of exception raised from parser execution. 52*9c5db199SXin Li 53*9c5db199SXin Li We will want to persist exceptions raised from the parser but also change 54*9c5db199SXin Li the objects that make them up during refactor. For this reason 55*9c5db199SXin Li we can't merely pickle the original. 56*9c5db199SXin Li """ 57*9c5db199SXin Li 58*9c5db199SXin Li def __init__(self, orig): 59*9c5db199SXin Li """ 60*9c5db199SXin Li Args: 61*9c5db199SXin Li orig: Exception; To copy 62*9c5db199SXin Li """ 63*9c5db199SXin Li self.classname = orig.__class__.__name__ 64*9c5db199SXin Li print("Copying exception:", self.classname) 65*9c5db199SXin Li for key, val in six.iteritems(orig.__dict__): 66*9c5db199SXin Li setattr(self, key, val) 67*9c5db199SXin Li 68*9c5db199SXin Li 69*9c5db199SXin Li def __eq__(self, other): 70*9c5db199SXin Li """Test if equal to another ParserException.""" 71*9c5db199SXin Li return self.__dict__ == other.__dict__ 72*9c5db199SXin Li 73*9c5db199SXin Li 74*9c5db199SXin Li def __ne__(self, other): 75*9c5db199SXin Li """Test if not equal to another ParserException.""" 76*9c5db199SXin Li return self.__dict__ != other.__dict__ 77*9c5db199SXin Li 78*9c5db199SXin Li 79*9c5db199SXin Li def __str__(self): 80*9c5db199SXin Li sd = self.__dict__ 81*9c5db199SXin Li pairs = ['%s="%s"' % (k, sd[k]) for k in sorted(sd.keys())] 82*9c5db199SXin Li return "<%s: %s>" % (self.classname, ', '.join(pairs)) 83*9c5db199SXin Li 84*9c5db199SXin Li 85*9c5db199SXin Liclass ParserTestResult(object): 86*9c5db199SXin Li """Abstract representation of test result parser state. 87*9c5db199SXin Li 88*9c5db199SXin Li We will want to persist test results but also change the 89*9c5db199SXin Li objects that make them up during refactor. For this reason 90*9c5db199SXin Li we can't merely pickle the originals. 91*9c5db199SXin Li """ 92*9c5db199SXin Li 93*9c5db199SXin Li def __init__(self, orig): 94*9c5db199SXin Li """ 95*9c5db199SXin Li Tracking all the attributes as they change over time is 96*9c5db199SXin Li not desirable. Instead we populate the instance's __dict__ 97*9c5db199SXin Li by introspecting orig. 98*9c5db199SXin Li 99*9c5db199SXin Li Args: 100*9c5db199SXin Li orig: testobj; Framework test result instance to copy. 101*9c5db199SXin Li """ 102*9c5db199SXin Li for key, val in six.iteritems(orig.__dict__): 103*9c5db199SXin Li if key == 'kernel': 104*9c5db199SXin Li setattr(self, key, dict(val.__dict__)) 105*9c5db199SXin Li elif key == 'iterations': 106*9c5db199SXin Li setattr(self, key, [dict(it.__dict__) for it in val]) 107*9c5db199SXin Li else: 108*9c5db199SXin Li setattr(self, key, val) 109*9c5db199SXin Li 110*9c5db199SXin Li 111*9c5db199SXin Li def __eq__(self, other): 112*9c5db199SXin Li """Test if equal to another ParserTestResult.""" 113*9c5db199SXin Li return self.__dict__ == other.__dict__ 114*9c5db199SXin Li 115*9c5db199SXin Li 116*9c5db199SXin Li def __ne__(self, other): 117*9c5db199SXin Li """Test if not equal to another ParserTestResult.""" 118*9c5db199SXin Li return self.__dict__ != other.__dict__ 119*9c5db199SXin Li 120*9c5db199SXin Li 121*9c5db199SXin Li def __str__(self): 122*9c5db199SXin Li sd = self.__dict__ 123*9c5db199SXin Li pairs = ['%s="%s"' % (k, sd[k]) for k in sorted(sd.keys())] 124*9c5db199SXin Li return "<%s: %s>" % (self.__class__.__name__, ', '.join(pairs)) 125*9c5db199SXin Li 126*9c5db199SXin Li 127*9c5db199SXin Lidef copy_parser_result(parser_result): 128*9c5db199SXin Li """Copy parser_result into ParserTestResult instances. 129*9c5db199SXin Li 130*9c5db199SXin Li Args: 131*9c5db199SXin Li parser_result: 132*9c5db199SXin Li list; [testobj, ...] 133*9c5db199SXin Li - Or - 134*9c5db199SXin Li Exception 135*9c5db199SXin Li 136*9c5db199SXin Li Returns: 137*9c5db199SXin Li list; [ParserTestResult, ...] 138*9c5db199SXin Li - Or - 139*9c5db199SXin Li ParserException 140*9c5db199SXin Li 141*9c5db199SXin Li Raises: 142*9c5db199SXin Li UnsupportedParserResultError; If parser_result type is not supported 143*9c5db199SXin Li """ 144*9c5db199SXin Li if type(parser_result) is list: 145*9c5db199SXin Li return [ParserTestResult(test) for test in parser_result] 146*9c5db199SXin Li elif isinstance(parser_result, Exception): 147*9c5db199SXin Li return ParserException(parser_result) 148*9c5db199SXin Li else: 149*9c5db199SXin Li raise UnsupportedParserResultError 150*9c5db199SXin Li 151*9c5db199SXin Li 152*9c5db199SXin Lidef compare_parser_results(left, right): 153*9c5db199SXin Li """Generates a textual report (for now) on the differences between. 154*9c5db199SXin Li 155*9c5db199SXin Li Args: 156*9c5db199SXin Li left: list of ParserTestResults or a single ParserException 157*9c5db199SXin Li right: list of ParserTestResults or a single ParserException 158*9c5db199SXin Li 159*9c5db199SXin Li Returns: Generator returned from difflib.Differ().compare() 160*9c5db199SXin Li """ 161*9c5db199SXin Li def to_los(obj): 162*9c5db199SXin Li """Generate a list of strings representation of object.""" 163*9c5db199SXin Li if type(obj) is list: 164*9c5db199SXin Li return [ 165*9c5db199SXin Li '%d) %s' % pair 166*9c5db199SXin Li for pair in zip(itertools.count(), obj)] 167*9c5db199SXin Li else: 168*9c5db199SXin Li return ['i) %s' % obj] 169*9c5db199SXin Li 170*9c5db199SXin Li return difflib.Differ().compare(to_los(left), to_los(right)) 171*9c5db199SXin Li 172*9c5db199SXin Li 173*9c5db199SXin Liclass ParserHarness(object): 174*9c5db199SXin Li """Harness for objects related to the parser. 175*9c5db199SXin Li 176*9c5db199SXin Li This can exercise a parser on specific result data in various ways. 177*9c5db199SXin Li """ 178*9c5db199SXin Li 179*9c5db199SXin Li def __init__( 180*9c5db199SXin Li self, parser, job, job_keyval, status_version, status_log_filepath): 181*9c5db199SXin Li """ 182*9c5db199SXin Li Args: 183*9c5db199SXin Li parser: tko.parsers.base.parser; Subclass instance of base parser. 184*9c5db199SXin Li job: job implementation; Returned from parser.make_job() 185*9c5db199SXin Li job_keyval: dict; Result of parsing job keyval file. 186*9c5db199SXin Li status_version: str; Status log format version 187*9c5db199SXin Li status_log_filepath: str; Path to result data status.log file 188*9c5db199SXin Li """ 189*9c5db199SXin Li self.parser = parser 190*9c5db199SXin Li self.job = job 191*9c5db199SXin Li self.job_keyval = job_keyval 192*9c5db199SXin Li self.status_version = status_version 193*9c5db199SXin Li self.status_log_filepath = status_log_filepath 194*9c5db199SXin Li 195*9c5db199SXin Li 196*9c5db199SXin Li def execute(self): 197*9c5db199SXin Li """Basic exercise, pass entire log data into .end() 198*9c5db199SXin Li 199*9c5db199SXin Li Returns: list; [testobj, ...] 200*9c5db199SXin Li """ 201*9c5db199SXin Li status_lines = open(self.status_log_filepath).readlines() 202*9c5db199SXin Li self.parser.start(self.job) 203*9c5db199SXin Li return self.parser.end(status_lines) 204*9c5db199SXin Li 205*9c5db199SXin Li 206*9c5db199SXin Liclass BaseScenarioTestCase(unittest_hotfix.TestCase): 207*9c5db199SXin Li """Base class for all Scenario TestCase implementations. 208*9c5db199SXin Li 209*9c5db199SXin Li This will load up all resources from scenario package directory upon 210*9c5db199SXin Li instantiation, and initialize a new ParserHarness before each test 211*9c5db199SXin Li method execution. 212*9c5db199SXin Li """ 213*9c5db199SXin Li def __init__(self, methodName='runTest'): 214*9c5db199SXin Li unittest_hotfix.TestCase.__init__(self, methodName) 215*9c5db199SXin Li self.package_dirpath = path.dirname( 216*9c5db199SXin Li sys.modules[self.__module__].__file__) 217*9c5db199SXin Li self.tmp_dirpath, self.results_dirpath = load_results_dir( 218*9c5db199SXin Li self.package_dirpath) 219*9c5db199SXin Li self.parser_result_store = load_parser_result_store( 220*9c5db199SXin Li self.package_dirpath) 221*9c5db199SXin Li self.config = load_config(self.package_dirpath) 222*9c5db199SXin Li self.parser_result_tag = self.config.get( 223*9c5db199SXin Li TEST, PARSER_RESULT_TAG) 224*9c5db199SXin Li self.expected_status_version = self.config.getint( 225*9c5db199SXin Li TEST, STATUS_VERSION) 226*9c5db199SXin Li self.harness = None 227*9c5db199SXin Li 228*9c5db199SXin Li 229*9c5db199SXin Li def setUp(self): 230*9c5db199SXin Li if self.results_dirpath: 231*9c5db199SXin Li self.harness = new_parser_harness(self.results_dirpath) 232*9c5db199SXin Li 233*9c5db199SXin Li 234*9c5db199SXin Li def tearDown(self): 235*9c5db199SXin Li if self.tmp_dirpath: 236*9c5db199SXin Li self.tmp_dirpath.clean() 237*9c5db199SXin Li 238*9c5db199SXin Li 239*9c5db199SXin Li def test_status_version(self): 240*9c5db199SXin Li """Ensure basic functionality.""" 241*9c5db199SXin Li self.skipIf(not self.harness) 242*9c5db199SXin Li self.assertEquals( 243*9c5db199SXin Li self.harness.status_version, self.expected_status_version) 244*9c5db199SXin Li 245*9c5db199SXin Li 246*9c5db199SXin Lidef shelve_open(filename, flag='c', protocol=None, writeback=False): 247*9c5db199SXin Li """A more system-portable wrapper around shelve.open, with the exact 248*9c5db199SXin Li same arguments and interpretation.""" 249*9c5db199SXin Li import dumbdbm 250*9c5db199SXin Li return shelve.Shelf(dumbdbm.open(filename, flag), protocol, writeback) 251*9c5db199SXin Li 252*9c5db199SXin Li 253*9c5db199SXin Lidef new_parser_harness(results_dirpath): 254*9c5db199SXin Li """Ensure valid environment and create new parser with wrapper. 255*9c5db199SXin Li 256*9c5db199SXin Li Args: 257*9c5db199SXin Li results_dirpath: str; Path to job results directory 258*9c5db199SXin Li 259*9c5db199SXin Li Returns: 260*9c5db199SXin Li ParserHarness; 261*9c5db199SXin Li 262*9c5db199SXin Li Raises: 263*9c5db199SXin Li BadResultsDirectoryError; If results dir does not exist or is malformed. 264*9c5db199SXin Li """ 265*9c5db199SXin Li if not path.exists(results_dirpath): 266*9c5db199SXin Li raise BadResultsDirectoryError 267*9c5db199SXin Li 268*9c5db199SXin Li keyval_path = path.join(results_dirpath, KEYVAL) 269*9c5db199SXin Li job_keyval = utils.read_keyval(keyval_path) 270*9c5db199SXin Li status_version = job_keyval[STATUS_VERSION] 271*9c5db199SXin Li parser = parser_lib.parser(status_version) 272*9c5db199SXin Li job = parser.make_job(results_dirpath) 273*9c5db199SXin Li status_log_filepath = path.join(results_dirpath, 'status.log') 274*9c5db199SXin Li if not path.exists(status_log_filepath): 275*9c5db199SXin Li raise BadResultsDirectoryError 276*9c5db199SXin Li 277*9c5db199SXin Li return ParserHarness( 278*9c5db199SXin Li parser, job, job_keyval, status_version, status_log_filepath) 279*9c5db199SXin Li 280*9c5db199SXin Li 281*9c5db199SXin Lidef store_parser_result(package_dirpath, parser_result, tag): 282*9c5db199SXin Li """Persist parser result to specified scenario package, keyed by tag. 283*9c5db199SXin Li 284*9c5db199SXin Li Args: 285*9c5db199SXin Li package_dirpath: str; Path to scenario package directory. 286*9c5db199SXin Li parser_result: list or Exception; Result from ParserHarness.execute 287*9c5db199SXin Li tag: str; Tag to use as shelve key for persisted parser_result 288*9c5db199SXin Li """ 289*9c5db199SXin Li copy = copy_parser_result(parser_result) 290*9c5db199SXin Li sto_filepath = path.join(package_dirpath, PARSER_RESULT_STORE) 291*9c5db199SXin Li sto = shelve_open(sto_filepath) 292*9c5db199SXin Li sto[tag] = copy 293*9c5db199SXin Li sto.close() 294*9c5db199SXin Li 295*9c5db199SXin Li 296*9c5db199SXin Lidef load_parser_result_store(package_dirpath, open_for_write=False): 297*9c5db199SXin Li """Load parser result store from specified scenario package. 298*9c5db199SXin Li 299*9c5db199SXin Li Args: 300*9c5db199SXin Li package_dirpath: str; Path to scenario package directory. 301*9c5db199SXin Li open_for_write: bool; Open store for writing. 302*9c5db199SXin Li 303*9c5db199SXin Li Returns: 304*9c5db199SXin Li shelve.DbfilenameShelf; Looks and acts like a dict 305*9c5db199SXin Li """ 306*9c5db199SXin Li open_flag = open_for_write and 'c' or 'r' 307*9c5db199SXin Li sto_filepath = path.join(package_dirpath, PARSER_RESULT_STORE) 308*9c5db199SXin Li return shelve_open(sto_filepath, flag=open_flag) 309*9c5db199SXin Li 310*9c5db199SXin Li 311*9c5db199SXin Lidef store_results_dir(package_dirpath, results_dirpath): 312*9c5db199SXin Li """Make tarball of results_dirpath in package_dirpath. 313*9c5db199SXin Li 314*9c5db199SXin Li Args: 315*9c5db199SXin Li package_dirpath: str; Path to scenario package directory. 316*9c5db199SXin Li results_dirpath: str; Path to job results directory 317*9c5db199SXin Li """ 318*9c5db199SXin Li tgz_filepath = path.join(package_dirpath, RESULTS_DIR_TARBALL) 319*9c5db199SXin Li tgz = tarfile.open(tgz_filepath, 'w:gz') 320*9c5db199SXin Li results_dirname = path.basename(results_dirpath) 321*9c5db199SXin Li tgz.add(results_dirpath, results_dirname) 322*9c5db199SXin Li tgz.close() 323*9c5db199SXin Li 324*9c5db199SXin Li 325*9c5db199SXin Lidef load_results_dir(package_dirpath): 326*9c5db199SXin Li """Unpack results tarball in package_dirpath to temp dir. 327*9c5db199SXin Li 328*9c5db199SXin Li Args: 329*9c5db199SXin Li package_dirpath: str; Path to scenario package directory. 330*9c5db199SXin Li 331*9c5db199SXin Li Returns: 332*9c5db199SXin Li str; New temp path for extracted results directory. 333*9c5db199SXin Li - Or - 334*9c5db199SXin Li None; If tarball does not exist 335*9c5db199SXin Li """ 336*9c5db199SXin Li tgz_filepath = path.join(package_dirpath, RESULTS_DIR_TARBALL) 337*9c5db199SXin Li if not path.exists(tgz_filepath): 338*9c5db199SXin Li return None, None 339*9c5db199SXin Li 340*9c5db199SXin Li tgz = tarfile.open(tgz_filepath, 'r:gz') 341*9c5db199SXin Li tmp_dirpath = autotemp.tempdir(unique_id='scenario_base') 342*9c5db199SXin Li results_dirname = tgz.next().name 343*9c5db199SXin Li tgz.extract(results_dirname, tmp_dirpath.name) 344*9c5db199SXin Li for info in tgz: 345*9c5db199SXin Li tgz.extract(info.name, tmp_dirpath.name) 346*9c5db199SXin Li return tmp_dirpath, path.join(tmp_dirpath.name, results_dirname) 347*9c5db199SXin Li 348*9c5db199SXin Li 349*9c5db199SXin Lidef write_config(package_dirpath, **properties): 350*9c5db199SXin Li """Write test configuration file to package_dirpath. 351*9c5db199SXin Li 352*9c5db199SXin Li Args: 353*9c5db199SXin Li package_dirpath: str; Path to scenario package directory. 354*9c5db199SXin Li properties: dict; Key value entries to write to to config file. 355*9c5db199SXin Li """ 356*9c5db199SXin Li config = six.moves.configparser.RawConfigParser() 357*9c5db199SXin Li config.add_section(TEST) 358*9c5db199SXin Li for key, val in six.iteritems(properties): 359*9c5db199SXin Li config.set(TEST, key, val) 360*9c5db199SXin Li 361*9c5db199SXin Li config_filepath = path.join(package_dirpath, CONFIG_FILENAME) 362*9c5db199SXin Li fi = open(config_filepath, 'w') 363*9c5db199SXin Li config.write(fi) 364*9c5db199SXin Li fi.close() 365*9c5db199SXin Li 366*9c5db199SXin Li 367*9c5db199SXin Lidef load_config(package_dirpath): 368*9c5db199SXin Li """Load config from package_dirpath. 369*9c5db199SXin Li 370*9c5db199SXin Li Args: 371*9c5db199SXin Li package_dirpath: str; Path to scenario package directory. 372*9c5db199SXin Li 373*9c5db199SXin Li Returns: 374*9c5db199SXin Li ConfigParser.RawConfigParser; 375*9c5db199SXin Li """ 376*9c5db199SXin Li config = six.moves.configparser.RawConfigParser() 377*9c5db199SXin Li config_filepath = path.join(package_dirpath, CONFIG_FILENAME) 378*9c5db199SXin Li config.read(config_filepath) 379*9c5db199SXin Li return config 380*9c5db199SXin Li 381*9c5db199SXin Li 382*9c5db199SXin Lidef install_unittest_module(package_dirpath, template_type): 383*9c5db199SXin Li """Install specified unittest template module to package_dirpath. 384*9c5db199SXin Li 385*9c5db199SXin Li Template modules are stored in tko/parsers/test/templates. 386*9c5db199SXin Li Installation includes: 387*9c5db199SXin Li Copying to package_dirpath/template_type_unittest.py 388*9c5db199SXin Li Copying scenario package common.py to package_dirpath 389*9c5db199SXin Li Touching package_dirpath/__init__.py 390*9c5db199SXin Li 391*9c5db199SXin Li Args: 392*9c5db199SXin Li package_dirpath: str; Path to scenario package directory. 393*9c5db199SXin Li template_type: str; Name of template module to install. 394*9c5db199SXin Li 395*9c5db199SXin Li Raises: 396*9c5db199SXin Li UnsupportedTemplateTypeError; If there is no module in 397*9c5db199SXin Li templates package called template_type. 398*9c5db199SXin Li """ 399*9c5db199SXin Li from_filepath = path.join( 400*9c5db199SXin Li TEMPLATES_DIRPATH, '%s.py' % template_type) 401*9c5db199SXin Li if not path.exists(from_filepath): 402*9c5db199SXin Li raise UnsupportedTemplateTypeError 403*9c5db199SXin Li 404*9c5db199SXin Li to_filepath = path.join( 405*9c5db199SXin Li package_dirpath, '%s_unittest.py' % template_type) 406*9c5db199SXin Li shutil.copy(from_filepath, to_filepath) 407*9c5db199SXin Li 408*9c5db199SXin Li # For convenience we must copy the common.py hack file too :-( 409*9c5db199SXin Li from_common_filepath = path.join( 410*9c5db199SXin Li TEMPLATES_DIRPATH, 'scenario_package_common.py') 411*9c5db199SXin Li to_common_filepath = path.join(package_dirpath, 'common.py') 412*9c5db199SXin Li shutil.copy(from_common_filepath, to_common_filepath) 413*9c5db199SXin Li 414*9c5db199SXin Li # And last but not least, touch an __init__ file 415*9c5db199SXin Li os.mknod(path.join(package_dirpath, '__init__.py')) 416*9c5db199SXin Li 417*9c5db199SXin Li 418*9c5db199SXin Lidef fix_package_dirname(package_dirname): 419*9c5db199SXin Li """Convert package_dirname to a valid package name string, if necessary. 420*9c5db199SXin Li 421*9c5db199SXin Li Args: 422*9c5db199SXin Li package_dirname: str; Name of scenario package directory. 423*9c5db199SXin Li 424*9c5db199SXin Li Returns: 425*9c5db199SXin Li str; Possibly fixed package_dirname 426*9c5db199SXin Li """ 427*9c5db199SXin Li # Really stupid atm, just enough to handle results dirnames 428*9c5db199SXin Li package_dirname = package_dirname.replace('-', '_') 429*9c5db199SXin Li pre = '' 430*9c5db199SXin Li if package_dirname[0].isdigit(): 431*9c5db199SXin Li pre = 'p' 432*9c5db199SXin Li return pre + package_dirname 433*9c5db199SXin Li 434*9c5db199SXin Li 435*9c5db199SXin Lidef sanitize_results_data(results_dirpath): 436*9c5db199SXin Li """Replace or remove any data that would possibly contain IP 437*9c5db199SXin Li 438*9c5db199SXin Li Args: 439*9c5db199SXin Li results_dirpath: str; Path to job results directory 440*9c5db199SXin Li """ 441*9c5db199SXin Li raise NotImplementedError 442