def test_trace_gyp(self): import trace_inputs expected_value = { 'conditions': [ [ 'OS=="%s"' % trace_inputs.get_flavor(), { 'variables': { 'isolate_dependency_tracked': [ # It is run from data/trace_inputs. '../trace_inputs.py', '../%s' % FILENAME, ], 'isolate_dependency_untracked': [ 'trace_inputs/', ], }, } ], ], } expected_buffer = cStringIO.StringIO() trace_inputs.pretty_print(expected_value, expected_buffer) cmd = [ '--cwd', 'data', '--product', '.', # Not tested. ] + self.command(True) actual = self._execute(cmd) self.assertEquals(expected_buffer.getvalue(), actual)
def test_trace_gyp(self): import trace_inputs expected_value = { 'conditions': [ ['OS=="%s"' % trace_inputs.get_flavor(), { 'variables': { 'isolate_dependency_tracked': [ # It is run from data/trace_inputs. '../trace_inputs.py', '../%s' % FILENAME, ], 'isolate_dependency_untracked': [ 'trace_inputs/', ], }, }], ], } expected_buffer = cStringIO.StringIO() trace_inputs.pretty_print(expected_value, expected_buffer) cmd = [ '--cwd', 'data', '--product', '.', # Not tested. ] + self.command(True) actual = self._execute(cmd) self.assertEquals(expected_buffer.getvalue(), actual)
def load_isolate(content, error): """Loads the .isolate file. Returns the command, dependencies and read_only flag. """ # Load the .isolate file, process its conditions, retrieve the command and # dependencies. configs = merge_isolate.load_gyp(merge_isolate.eval_content(content)) flavor = trace_inputs.get_flavor() config = configs.per_os.get(flavor) or configs.per_os.get(None) if not config: error('Failed to load configuration for \'%s\'' % flavor) # Merge tracked and untracked dependencies, isolate.py doesn't care about the # trackability of the dependencies, only the build tool does. return config.command, config.tracked + config.untracked, config.read_only
def process_inputs(prevdict, indir, infiles, level, read_only): """Returns a dictionary of input files, populated with the files' mode and hash. |prevdict| is the previous dictionary. It is used to retrieve the cached sha-1 to skip recalculating the hash. |level| determines the amount of information retrieved. 1 loads no information. 2 loads minimal stat() information. 3 calculates the sha-1 of the file's content. The file mode is manipulated if read_only is True. In practice, we only save one of 4 modes: 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r). On windows, mode is not set since all files are 'executable' by default. """ assert level in (NO_INFO, STATS_ONLY, WITH_HASH) outdict = {} for infile in infiles: filepath = os.path.join(indir, infile) outdict[infile] = {} if level >= STATS_ONLY: filestats = os.stat(filepath) if trace_inputs.get_flavor() != 'win': filemode = stat.S_IMODE(filestats.st_mode) # Remove write access for non-owner. filemode &= ~(stat.S_IWGRP | stat.S_IWOTH) if read_only: filemode &= ~stat.S_IWUSR if filemode & stat.S_IXUSR: filemode |= (stat.S_IXGRP | stat.S_IXOTH) else: filemode &= ~(stat.S_IXGRP | stat.S_IXOTH) outdict[infile]['mode'] = filemode outdict[infile]['size'] = filestats.st_size # Used to skip recalculating the hash. Use the most recent update time. outdict[infile]['timestamp'] = int(round(filestats.st_mtime)) # If the timestamp wasn't updated, carry on the sha-1. if (prevdict.get(infile, {}).get('timestamp') == outdict[infile]['timestamp'] and 'sha-1' in prevdict[infile]): # Reuse the previous hash. outdict[infile]['sha-1'] = prevdict[infile]['sha-1'] if level >= WITH_HASH and not outdict[infile].get('sha-1'): h = hashlib.sha1() with open(filepath, 'rb') as f: h.update(f.read()) outdict[infile]['sha-1'] = h.hexdigest() return outdict
def load_isolate(content, error): """Loads the .isolate file and returns the information unprocessed. Returns the command, dependencies and read_only flag. The dependencies are fixed to use os.path.sep. """ # Load the .isolate file, process its conditions, retrieve the command and # dependencies. configs = merge_isolate.load_gyp(merge_isolate.eval_content(content)) flavor = trace_inputs.get_flavor() config = configs.per_os.get(flavor) or configs.per_os.get(None) if not config: error("Failed to load configuration for '%s'" % flavor) # Merge tracked and untracked dependencies, isolate.py doesn't care about the # trackability of the dependencies, only the build tool does. dependencies = [f.replace("/", os.path.sep) for f in config.tracked + config.untracked] return config.command, dependencies, config.read_only
def load_isolate(content, variables, error): """Loads the .isolate file. Returns the command, dependencies and read_only flag. """ # Load the .isolate file, process its conditions, retrieve the command and # dependencies. configs = merge_isolate.load_gyp(merge_isolate.eval_content(content)) flavor = trace_inputs.get_flavor() config = configs.per_os.get(flavor) or configs.per_os.get(None) if not config: error('Failed to load configuration for \'%s\'' % flavor) # Convert the variables and merge tracked and untracked dependencies. # isolate.py doesn't care about the trackability of the dependencies. infiles = [eval_variables(f, variables) for f in config.tracked ] + [eval_variables(f, variables) for f in config.untracked] command = [eval_variables(i, variables) for i in config.command] return command, infiles, config.read_only
def process_input(filepath, prevdict, level, read_only): """Processes an input file, a dependency, and return meta data about it. Arguments: - filepath: File to act on. - prevdict: the previous dictionary. It is used to retrieve the cached sha-1 to skip recalculating the hash. - level: determines the amount of information retrieved. - read_only: If True, the file mode is manipulated. In practice, only save one of 4 modes: 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r). On windows, mode is not set since all files are 'executable' by default. """ assert level in (NO_INFO, STATS_ONLY, WITH_HASH) out = {} if level >= STATS_ONLY: filestats = os.stat(filepath) if trace_inputs.get_flavor() != 'win': filemode = stat.S_IMODE(filestats.st_mode) # Remove write access for non-owner. filemode &= ~(stat.S_IWGRP | stat.S_IWOTH) if read_only: filemode &= ~stat.S_IWUSR if filemode & stat.S_IXUSR: filemode |= (stat.S_IXGRP | stat.S_IXOTH) else: filemode &= ~(stat.S_IXGRP | stat.S_IXOTH) out['mode'] = filemode out['size'] = filestats.st_size # Used to skip recalculating the hash. Use the most recent update time. out['timestamp'] = int(round(filestats.st_mtime)) # If the timestamp wasn't updated, carry on the sha-1. if (prevdict.get('timestamp') == out['timestamp'] and 'sha-1' in prevdict): # Reuse the previous hash. out['sha-1'] = prevdict['sha-1'] if level >= WITH_HASH and not out.get('sha-1'): h = hashlib.sha1() with open(filepath, 'rb') as f: h.update(f.read()) out['sha-1'] = h.hexdigest() return out
def main(): """Handles CLI and normalizes the input arguments to pass them to isolate(). """ default_variables = [('OS', trace_inputs.get_flavor())] if sys.platform in ('win32', 'cygwin'): default_variables.append(('EXECUTABLE_SUFFIX', '.exe')) else: default_variables.append(('EXECUTABLE_SUFFIX', '')) valid_modes = sorted(VALID_MODES.keys() + ['noop']) parser = optparse.OptionParser( usage='%prog [options] [.isolate file]', description=sys.modules[__name__].__doc__) parser.format_description = lambda *_: parser.description parser.add_option( '-v', '--verbose', action='count', default=int(os.environ.get('ISOLATE_DEBUG', 0)), help='Use multiple times') parser.add_option( '-m', '--mode', choices=valid_modes, help='Determines the action to be taken: %s' % ', '.join(valid_modes)) parser.add_option( '-r', '--result', metavar='FILE', help='Result file to store the json manifest') parser.add_option( '-V', '--variable', nargs=2, action='append', default=default_variables, dest='variables', metavar='FOO BAR', help='Variables to process in the .isolate file, default: %default') parser.add_option( '-o', '--outdir', metavar='DIR', help='Directory used to recreate the tree or store the hash table. ' 'If the environment variable ISOLATE_HASH_TABLE_DIR exists, it will ' 'be used. Otherwise, for run and remap, uses a /tmp subdirectory. ' 'For the other modes, defaults to the directory containing --result') options, args = parser.parse_args() levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] logging.basicConfig( level=levels[min(len(levels)-1, options.verbose)], format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') if not options.mode: parser.error('--mode is required') if not options.result: parser.error('--result is required.') if len(args) > 1: logging.debug('%s' % sys.argv) parser.error('Use only one argument which should be a .isolate file') # Make sure the paths make sense. On Windows, / and \ are often mixed together # in a path. result_file = os.path.abspath(options.result.replace('/', os.path.sep)) if options.mode == 'noop': # This undocumented mode is to help transition since some builders do not # have all the test data files checked out. Touch result_file and exit # silently. open(result_file, 'a').close() return 0 # input_file may be None. input_file = ( os.path.abspath(args[0].replace('/', os.path.sep)) if args else None) # out_dir may be None. out_dir = ( os.path.abspath(options.outdir.replace('/', os.path.sep)) if options.outdir else None) # Fix variables. variables = dict(options.variables) # After basic validation, pass this to isolate(). return isolate( result_file, input_file, options.mode, variables, out_dir, parser.error)
def main(): default_variables = ['OS=%s' % trace_inputs.get_flavor()] if sys.platform in ('win32', 'cygwin'): default_variables.append('EXECUTABLE_SUFFIX=.exe') else: default_variables.append('EXECUTABLE_SUFFIX=') valid_modes = get_valid_modes() parser = optparse.OptionParser(usage='%prog [options] [.isolate file]', description=sys.modules[__name__].__doc__) parser.format_description = lambda *_: parser.description parser.add_option('-v', '--verbose', action='count', default=int(os.environ.get('ISOLATE_DEBUG', 0)), help='Use multiple times') parser.add_option('-m', '--mode', choices=valid_modes, help='Determines the action to be taken: %s' % ', '.join(valid_modes)) parser.add_option('-r', '--result', metavar='FILE', help='Result file to store the json manifest') parser.add_option( '-V', '--variable', action='append', default=default_variables, dest='variables', metavar='FOO=BAR', help='Variables to process in the .isolate file, default: %default') parser.add_option( '-o', '--outdir', metavar='DIR', help='Directory used to recreate the tree or store the hash table. ' 'If the environment variable ISOLATE_HASH_TABLE_DIR exists, it will ' 'be used. Otherwise, for run and remap, uses a /tmp subdirectory. ' 'For the other modes, defaults to the directory containing --result') options, args = parser.parse_args() level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] logging.basicConfig( level=level, format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') if not options.mode: parser.error('--mode is required') if len(args) != 1: parser.error('Use only one argument which should be a .isolate file') input_file = os.path.abspath(args[0]) isolate_dir = os.path.dirname(input_file) # Extract the variables. variables = dict(i.split('=', 1) for i in options.variables) # Process path variables as a special case. First normalize it, verifies it # exists, convert it to an absolute path, then set it as relative to # isolate_dir. for i in ('PRODUCT_DIR', ): if i not in variables: continue variable = os.path.normpath(variables[i]) if not os.path.isdir(variable): parser.error('%s=%s is not a directory' % (i, variable)) variable = os.path.abspath(variable) # All variables are relative to the input file. variables[i] = os.path.relpath(variable, isolate_dir) command, infiles, read_only = load_isolate( open(input_file, 'r').read(), variables, parser.error) # The trick used to determine the root directory is to look at "how far" back # up it is looking up. root_dir = isolate_dir.replace(os.path.sep, '/') for i in infiles: i = i.replace(os.path.sep, '/') x = isolate_dir.replace(os.path.sep, '/') while i.startswith('../'): i = i[3:] assert not i.startswith('/') x = posixpath.dirname(x) if root_dir.startswith(x): root_dir = x root_dir = root_dir.replace('/', os.path.sep) # The relative directory is automatically determined by the relative path # between root_dir and the directory containing the .isolate file. relative_dir = os.path.relpath(isolate_dir, root_dir) logging.debug('relative_dir: %s' % relative_dir) logging.debug('variables: %s' % ', '.join('%s=%s' % (k, v) for k, v in variables.iteritems())) logging.debug('command: %s' % command) logging.debug('infiles: %s' % infiles) logging.debug('read_only: %s' % read_only) infiles = [normpath(os.path.join(relative_dir, f)) for f in infiles] logging.debug('processed infiles: %s' % infiles) try: return isolate(options.outdir, root_dir, infiles, options.mode, read_only, command, relative_dir, options.result) except run_test_from_archive.MappingError, e: print >> sys.stderr, str(e) return 1
def main(): default_variables = [('OS', trace_inputs.get_flavor())] if sys.platform in ('win32', 'cygwin'): default_variables.append(('EXECUTABLE_SUFFIX', '.exe')) else: default_variables.append(('EXECUTABLE_SUFFIX', '')) valid_modes = get_valid_modes() + ['noop'] parser = optparse.OptionParser(usage='%prog [options] [.isolate file]', description=sys.modules[__name__].__doc__) parser.format_description = lambda *_: parser.description parser.add_option('-v', '--verbose', action='count', default=int(os.environ.get('ISOLATE_DEBUG', 0)), help='Use multiple times') parser.add_option('-m', '--mode', choices=valid_modes, help='Determines the action to be taken: %s' % ', '.join(valid_modes)) parser.add_option('-r', '--result', metavar='FILE', help='Result file to store the json manifest') parser.add_option( '-V', '--variable', nargs=2, action='append', default=default_variables, dest='variables', metavar='FOO BAR', help='Variables to process in the .isolate file, default: %default') parser.add_option( '-o', '--outdir', metavar='DIR', help='Directory used to recreate the tree or store the hash table. ' 'If the environment variable ISOLATE_HASH_TABLE_DIR exists, it will ' 'be used. Otherwise, for run and remap, uses a /tmp subdirectory. ' 'For the other modes, defaults to the directory containing --result') options, args = parser.parse_args() level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] logging.basicConfig( level=level, format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') if not options.mode: parser.error('--mode is required') if len(args) != 1: logging.debug('%s' % sys.argv) parser.error('Use only one argument which should be a .isolate file') if options.mode == 'noop': # This undocumented mode is to help transition since some builders do not # have all the test data files checked out. Exit silently. return 0 root_dir, infiles, data = process_options(dict(options.variables), options.result, args[0], parser.error) try: resultcode, data = isolate(options.outdir, options.mode, root_dir, infiles, data) except run_test_from_archive.MappingError, e: print >> sys.stderr, str(e) return 1
def main(): """Handles CLI and normalizes the input arguments to pass them to isolate(). """ default_variables = [("OS", trace_inputs.get_flavor())] if sys.platform in ("win32", "cygwin"): default_variables.append(("EXECUTABLE_SUFFIX", ".exe")) else: default_variables.append(("EXECUTABLE_SUFFIX", "")) valid_modes = sorted(VALID_MODES.keys() + ["noop"]) parser = optparse.OptionParser(usage="%prog [options] [.isolate file]", description=sys.modules[__name__].__doc__) parser.format_description = lambda *_: parser.description parser.add_option( "-v", "--verbose", action="count", default=int(os.environ.get("ISOLATE_DEBUG", 0)), help="Use multiple times" ) parser.add_option( "-m", "--mode", choices=valid_modes, help="Determines the action to be taken: %s" % ", ".join(valid_modes) ) parser.add_option("-r", "--result", metavar="FILE", help="Result file to store the json manifest") parser.add_option( "-V", "--variable", nargs=2, action="append", default=default_variables, dest="variables", metavar="FOO BAR", help="Variables to process in the .isolate file, default: %default", ) parser.add_option( "-o", "--outdir", metavar="DIR", help="Directory used to recreate the tree or store the hash table. " "If the environment variable ISOLATE_HASH_TABLE_DIR exists, it will " "be used. Otherwise, for run and remap, uses a /tmp subdirectory. " "For the other modes, defaults to the directory containing --result", ) options, args = parser.parse_args() levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] logging.basicConfig( level=levels[min(len(levels) - 1, options.verbose)], format="%(levelname)5s %(module)15s(%(lineno)3d): %(message)s", ) if not options.mode: parser.error("--mode is required") if not options.result: parser.error("--result is required.") if len(args) > 1: logging.debug("%s" % sys.argv) parser.error("Use only one argument which should be a .isolate file") if options.mode == "noop": # This undocumented mode is to help transition since some builders do not # have all the test data files checked out. Exit silently. return 0 # Make sure the paths make sense. On Windows, / and \ are often mixed together # in a path. result_file = os.path.abspath(options.result.replace("/", os.path.sep)) # input_file may be None. input_file = os.path.abspath(args[0].replace("/", os.path.sep)) if args else None # out_dir may be None. out_dir = os.path.abspath(options.outdir.replace("/", os.path.sep)) if options.outdir else None # Fix variables. variables = dict(options.variables) # After basic validation, pass this to isolate(). return isolate(result_file, input_file, options.mode, variables, out_dir, parser.error)
def main(): default_variables = [('OS', trace_inputs.get_flavor())] if sys.platform in ('win32', 'cygwin'): default_variables.append(('EXECUTABLE_SUFFIX', '.exe')) else: default_variables.append(('EXECUTABLE_SUFFIX', '')) valid_modes = get_valid_modes() + ['noop'] parser = optparse.OptionParser( usage='%prog [options] [.isolate file]', description=sys.modules[__name__].__doc__) parser.format_description = lambda *_: parser.description parser.add_option( '-v', '--verbose', action='count', default=int(os.environ.get('ISOLATE_DEBUG', 0)), help='Use multiple times') parser.add_option( '-m', '--mode', choices=valid_modes, help='Determines the action to be taken: %s' % ', '.join(valid_modes)) parser.add_option( '-r', '--result', metavar='FILE', help='Result file to store the json manifest') parser.add_option( '-V', '--variable', nargs=2, action='append', default=default_variables, dest='variables', metavar='FOO BAR', help='Variables to process in the .isolate file, default: %default') parser.add_option( '-o', '--outdir', metavar='DIR', help='Directory used to recreate the tree or store the hash table. ' 'If the environment variable ISOLATE_HASH_TABLE_DIR exists, it will ' 'be used. Otherwise, for run and remap, uses a /tmp subdirectory. ' 'For the other modes, defaults to the directory containing --result') options, args = parser.parse_args() level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] logging.basicConfig( level=level, format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') if not options.mode: parser.error('--mode is required') if len(args) != 1: logging.debug('%s' % sys.argv) parser.error('Use only one argument which should be a .isolate file') if options.mode == 'noop': # This undocumented mode is to help transition since some builders do not # have all the test data files checked out. Exit silently. return 0 root_dir, infiles, data = process_options( dict(options.variables), options.result, args[0], parser.error) try: resultcode, data = isolate( options.outdir, options.mode, root_dir, infiles, data) except run_test_from_archive.MappingError, e: print >> sys.stderr, str(e) return 1
def main(): """Handles CLI and normalizes the input arguments to pass them to isolate(). """ default_variables = [('OS', trace_inputs.get_flavor())] if sys.platform in ('win32', 'cygwin'): default_variables.append(('EXECUTABLE_SUFFIX', '.exe')) else: default_variables.append(('EXECUTABLE_SUFFIX', '')) valid_modes = sorted(VALID_MODES.keys() + ['noop']) parser = optparse.OptionParser(usage='%prog [options] [.isolate file]', description=sys.modules[__name__].__doc__) parser.format_description = lambda *_: parser.description parser.add_option('-v', '--verbose', action='count', default=int(os.environ.get('ISOLATE_DEBUG', 0)), help='Use multiple times') parser.add_option('-m', '--mode', choices=valid_modes, help='Determines the action to be taken: %s' % ', '.join(valid_modes)) parser.add_option('-r', '--result', metavar='FILE', help='Result file to store the json manifest') parser.add_option( '-V', '--variable', nargs=2, action='append', default=default_variables, dest='variables', metavar='FOO BAR', help='Variables to process in the .isolate file, default: %default') parser.add_option( '-o', '--outdir', metavar='DIR', help='Directory used to recreate the tree or store the hash table. ' 'If the environment variable ISOLATE_HASH_TABLE_DIR exists, it will ' 'be used. Otherwise, for run and remap, uses a /tmp subdirectory. ' 'For the other modes, defaults to the directory containing --result') options, args = parser.parse_args() levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] logging.basicConfig( level=levels[min(len(levels) - 1, options.verbose)], format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s') if not options.mode: parser.error('--mode is required') if not options.result: parser.error('--result is required.') if len(args) > 1: logging.debug('%s' % sys.argv) parser.error('Use only one argument which should be a .isolate file') # Make sure the paths make sense. On Windows, / and \ are often mixed together # in a path. result_file = os.path.abspath(options.result.replace('/', os.path.sep)) if options.mode == 'noop': # This undocumented mode is to help transition since some builders do not # have all the test data files checked out. Touch result_file and exit # silently. open(result_file, 'a').close() return 0 # input_file may be None. input_file = (os.path.abspath(args[0].replace('/', os.path.sep)) if args else None) # out_dir may be None. out_dir = (os.path.abspath(options.outdir.replace('/', os.path.sep)) if options.outdir else None) # Fix variables. variables = dict(options.variables) # After basic validation, pass this to isolate(). return isolate(result_file, input_file, options.mode, variables, out_dir, parser.error)
test_cases = ( ('"foo"', ['foo']), ('"foo", "bar"', ['foo', 'bar']), ('"foo"..., "bar"', ['foo', 'bar']), ('"foo", "bar"...', ['foo', 'bar']), ) for actual, expected in test_cases: self.assertEquals(expected, trace_inputs.process_quoted_arguments(actual)) def join_norm(*args): """Joins and normalizes path in a single step.""" return unicode(os.path.normpath(os.path.join(*args))) if trace_inputs.get_flavor() == 'linux': class StraceInputs(unittest.TestCase): # Represents the root process pid (an arbitrary number). _ROOT_PID = 27 _CHILD_PID = 14 _GRAND_CHILD_PID = 70 @staticmethod def _load_context(lines, initial_cwd): context = trace_inputs.Strace.Context(lambda _: False, initial_cwd) for line in lines: context.on_line(*line) return context.to_results().flatten() def _test_lines(self, lines, initial_cwd, files, command=None): filepath = join_norm(initial_cwd, '../out/unittests')