def __init__(self, cwd): assert os.path.isdir(cwd) self.cwd = cwd self.log_manager = LoggingManager() self.logger = logging.getLogger(__name__) self.settings = ConfigSettings() self.log_manager.register_structured_logger(self.logger)
def __init__(self, cwd): global MODULES_SCANNED assert os.path.isdir(cwd) self.cwd = cwd self.log_manager = LoggingManager() self.logger = logging.getLogger(__name__) self.settings = ConfigSettings() self.log_manager.register_structured_logger(self.logger) if not MODULES_SCANNED: self._load_modules() MODULES_SCANNED = True
def test_simple(self): s = ConfigSettings() s.register_provider(Provider1) self.assertEqual(len(s), 1) self.assertIn('foo', s) foo = s['foo'] foo = s.foo self.assertEqual(len(foo), 2) self.assertIn('bar', foo) self.assertIn('baz', foo) foo['bar'] = 'value1' self.assertEqual(foo['bar'], 'value1') self.assertEqual(foo['bar'], 'value1')
def test_retrieval_type(self): s = ConfigSettings() s.register_provider(Provider2) a = s.a a.string = 'foo' a.boolean = True a.pos_int = 12 a.int = -4 a.abs_path = '/home/gps' a.rel_path = 'foo.c' a.path = './foo/bar' self.assertIsInstance(a.string, str_type) self.assertIsInstance(a.boolean, bool) self.assertIsInstance(a.pos_int, int) self.assertIsInstance(a.int, int) self.assertIsInstance(a.abs_path, str_type) self.assertIsInstance(a.rel_path, str_type) self.assertIsInstance(a.path, str_type)
def test_write_pot(self): s = ConfigSettings() s.register_provider(Provider1) s.register_provider(Provider2) # Just a basic sanity test. temp = NamedTemporaryFile('wt') s.write_pot(temp) temp.flush()
def test_file_reading_single(self): temp = NamedTemporaryFile(mode='wt') temp.write(CONFIG1) temp.flush() s = ConfigSettings() s.register_provider(Provider1) s.load_file(temp.name) self.assertEqual(s.foo.bar, 'bar_value')
def test_file_reading_multiple(self): """Loading multiple files has proper overwrite behavior.""" temp1 = NamedTemporaryFile(mode='wt') temp1.write(CONFIG1) temp1.flush() temp2 = NamedTemporaryFile(mode='wt') temp2.write(CONFIG2) temp2.flush() s = ConfigSettings() s.register_provider(Provider1) s.load_files([temp1.name, temp2.name]) self.assertEqual(s.foo.bar, 'value2')
class Mach(object): """Contains code for the command-line `mach` interface.""" USAGE = """%(prog)s [global arguments] command [command arguments] mach (German for "do") is the main interface to the Mozilla build system and common developer tasks. You tell mach the command you want to perform and it does it for you. Some common commands are: %(prog)s build Build/compile the source tree. %(prog)s test Run tests. %(prog)s help Show full help, including the list of all commands. To see more help for a specific command, run: %(prog)s <command> --help """ def __init__(self, cwd): assert os.path.isdir(cwd) self.cwd = cwd self.log_manager = LoggingManager() self.logger = logging.getLogger(__name__) self.settings = ConfigSettings() self.log_manager.register_structured_logger(self.logger) def run(self, argv): """Runs mach with arguments provided from the command line.""" # If no encoding is defined, we default to UTF-8 because without this # Python 2.7 will assume the default encoding of ASCII. This will blow # up with UnicodeEncodeError as soon as it encounters a non-ASCII # character in a unicode instance. We simply install a wrapper around # the streams and restore once we have finished. orig_stdin = sys.stdin orig_stdout = sys.stdout orig_stderr = sys.stderr try: if sys.stdin.encoding is None: sys.stdin = codecs.getreader('utf-8')(sys.stdin) if sys.stdout.encoding is None: sys.stdout = codecs.getwriter('utf-8')(sys.stdout) if sys.stderr.encoding is None: sys.stderr = codecs.getwriter('utf-8')(sys.stderr) self._run(argv) finally: sys.stdin = orig_stdin sys.stdout = orig_stdout sys.stderr = orig_stderr def _run(self, argv): parser = self.get_argument_parser() if not len(argv): # We don't register the usage until here because if it is globally # registered, argparse always prints it. This is not desired when # running with --help. parser.usage = Mach.USAGE parser.print_usage() return 0 if argv[0] == 'help': parser.print_help() return 0 args = parser.parse_args(argv) # Add JSON logging to a file if requested. if args.logfile: self.log_manager.add_json_handler(args.logfile) # Up the logging level if requested. log_level = logging.INFO if args.verbose: log_level = logging.DEBUG # Always enable terminal logging. The log manager figures out if we are # actually in a TTY or are a pipe and does the right thing. self.log_manager.add_terminal_logging(level=log_level, write_interval=args.log_interval) self.load_settings(args) conf = BuildConfig(self.settings) stripped = {k: getattr(args, k) for k in vars(args) if k not in CONSUMED_ARGUMENTS} # If the command is associated with a class, instantiate and run it. # All classes must be Base-derived and take the expected argument list. if hasattr(args, 'cls'): cls = getattr(args, 'cls') instance = cls(self.cwd, self.settings, self.log_manager) fn = getattr(instance, getattr(args, 'method')) # If the command is associated with a function, call it. elif hasattr(args, 'func'): fn = getattr(args, 'func') else: raise Exception('Dispatch configuration error in module.') fn(**stripped) def log(self, level, action, params, format_str): """Helper method to record a structured log event.""" self.logger.log(level, format_str, extra={'action': action, 'params': params}) def load_settings(self, args): """Determine which settings files apply and load them. Currently, we only support loading settings from a single file. Ideally, we support loading from multiple files. This is supported by the ConfigSettings API. However, that API currently doesn't track where individual values come from, so if we load from multiple sources then save, we effectively do a full copy. We don't want this. Until ConfigSettings does the right thing, we shouldn't expose multi-file loading. We look for a settings file in the following locations. The first one found wins: 1) Command line argument 2) Environment variable 3) Default path """ for provider in SETTINGS_PROVIDERS: provider.register_settings() self.settings.register_provider(provider) p = os.path.join(self.cwd, 'mach.ini') if args.settings_file: p = args.settings_file elif 'MACH_SETTINGS_FILE' in os.environ: p = os.environ['MACH_SETTINGS_FILE'] self.settings.load_file(p) return os.path.exists(p) def get_argument_parser(self): """Returns an argument parser for the command-line interface.""" parser = ArgumentParser(add_help=False, usage='%(prog)s [global arguments] command [command arguments]') # Order is important here as it dictates the order the auto-generated # help messages are printed. subparser = parser.add_subparsers(dest='command', title='Commands') parser.set_defaults(command='help') global_group = parser.add_argument_group('Global Arguments') global_group.add_argument('-h', '--help', action='help', help='Show this help message and exit.') global_group.add_argument('--settings', dest='settings_file', metavar='FILENAME', help='Path to settings file.') global_group.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Print verbose output.') global_group.add_argument('-l', '--log-file', dest='logfile', metavar='FILENAME', type=argparse.FileType('ab'), help='Filename to write log data to.') global_group.add_argument('--log-interval', dest='log_interval', action='store_true', default=False, help='Prefix log line with interval from last message rather ' 'than relative time. Note that this is NOT execution time ' 'if there are parallel operations.') # Register argument action providers with us. for cls in HANDLERS: cls.populate_argparse(subparser) return parser
class Mach(object): """Contains code for the command-line `mach` interface.""" USAGE = """%(prog)s [global arguments] command [command arguments] mach (German for "do") is the main interface to the Mozilla build system and common developer tasks. You tell mach the command you want to perform and it does it for you. Some common commands are: %(prog)s build Build/compile the source tree. %(prog)s test Run tests. %(prog)s help Show full help, including the list of all commands. To see more help for a specific command, run: %(prog)s <command> --help """ def __init__(self, cwd): assert os.path.isdir(cwd) self.cwd = cwd self.log_manager = LoggingManager() self.logger = logging.getLogger(__name__) self.settings = ConfigSettings() self.log_manager.register_structured_logger(self.logger) def run(self, argv): """Runs mach with arguments provided from the command line.""" # If no encoding is defined, we default to UTF-8 because without this # Python 2.7 will assume the default encoding of ASCII. This will blow # up with UnicodeEncodeError as soon as it encounters a non-ASCII # character in a unicode instance. We simply install a wrapper around # the streams and restore once we have finished. orig_stdin = sys.stdin orig_stdout = sys.stdout orig_stderr = sys.stderr try: if sys.stdin.encoding is None: sys.stdin = codecs.getreader('utf-8')(sys.stdin) if sys.stdout.encoding is None: sys.stdout = codecs.getwriter('utf-8')(sys.stdout) if sys.stderr.encoding is None: sys.stderr = codecs.getwriter('utf-8')(sys.stderr) self._run(argv) finally: sys.stdin = orig_stdin sys.stdout = orig_stdout sys.stderr = orig_stderr def _run(self, argv): parser = self.get_argument_parser() if not len(argv): # We don't register the usage until here because if it is globally # registered, argparse always prints it. This is not desired when # running with --help. parser.usage = Mach.USAGE parser.print_usage() return 0 if argv[0] == 'help': parser.print_help() return 0 args = parser.parse_args(argv) # Add JSON logging to a file if requested. if args.logfile: self.log_manager.add_json_handler(args.logfile) # Up the logging level if requested. log_level = logging.INFO if args.verbose: log_level = logging.DEBUG # Always enable terminal logging. The log manager figures out if we are # actually in a TTY or are a pipe and does the right thing. self.log_manager.add_terminal_logging(level=log_level, write_interval=args.log_interval) self.load_settings(args) conf = BuildConfig(self.settings) stripped = { k: getattr(args, k) for k in vars(args) if k not in CONSUMED_ARGUMENTS } # If the command is associated with a class, instantiate and run it. # All classes must be Base-derived and take the expected argument list. if hasattr(args, 'cls'): cls = getattr(args, 'cls') instance = cls(self.cwd, self.settings, self.log_manager) fn = getattr(instance, getattr(args, 'method')) # If the command is associated with a function, call it. elif hasattr(args, 'func'): fn = getattr(args, 'func') else: raise Exception('Dispatch configuration error in module.') fn(**stripped) def log(self, level, action, params, format_str): """Helper method to record a structured log event.""" self.logger.log(level, format_str, extra={ 'action': action, 'params': params }) def load_settings(self, args): """Determine which settings files apply and load them. Currently, we only support loading settings from a single file. Ideally, we support loading from multiple files. This is supported by the ConfigSettings API. However, that API currently doesn't track where individual values come from, so if we load from multiple sources then save, we effectively do a full copy. We don't want this. Until ConfigSettings does the right thing, we shouldn't expose multi-file loading. We look for a settings file in the following locations. The first one found wins: 1) Command line argument 2) Environment variable 3) Default path """ for provider in SETTINGS_PROVIDERS: provider.register_settings() self.settings.register_provider(provider) p = os.path.join(self.cwd, 'mach.ini') if args.settings_file: p = args.settings_file elif 'MACH_SETTINGS_FILE' in os.environ: p = os.environ['MACH_SETTINGS_FILE'] self.settings.load_file(p) return os.path.exists(p) def get_argument_parser(self): """Returns an argument parser for the command-line interface.""" parser = ArgumentParser( add_help=False, usage='%(prog)s [global arguments] command [command arguments]') # Order is important here as it dictates the order the auto-generated # help messages are printed. subparser = parser.add_subparsers(dest='command', title='Commands') parser.set_defaults(command='help') global_group = parser.add_argument_group('Global Arguments') global_group.add_argument('-h', '--help', action='help', help='Show this help message and exit.') global_group.add_argument('--settings', dest='settings_file', metavar='FILENAME', help='Path to settings file.') global_group.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Print verbose output.') global_group.add_argument('-l', '--log-file', dest='logfile', metavar='FILENAME', type=argparse.FileType('ab'), help='Filename to write log data to.') global_group.add_argument( '--log-interval', dest='log_interval', action='store_true', default=False, help='Prefix log line with interval from last message rather ' 'than relative time. Note that this is NOT execution time ' 'if there are parallel operations.') # Register argument action providers with us. for cls in HANDLERS: cls.populate_argparse(subparser) return parser
def get_base(self): settings = ConfigSettings() settings.register_provider(BuildConfig) return MozbuildObject(topsrcdir, settings, log_manager)
def test_basic(self): c = ConfigSettings() c.register_provider(BuildConfig) c.build.threads = 6
def test_empty(self): s = ConfigSettings() self.assertEqual(len(s), 0) self.assertNotIn('foo', s)
def test_file_writing(self): s = ConfigSettings() s.register_provider(Provider2) s.a.string = 'foo' s.a.boolean = False temp = NamedTemporaryFile('wt') s.write(temp) temp.flush() s2 = ConfigSettings() s2.register_provider(Provider2) s2.load_file(temp.name) self.assertEqual(s.a.string, s2.a.string) self.assertEqual(s.a.boolean, s2.a.boolean)
def test_file_reading_missing(self): """Missing files should silently be ignored.""" s = ConfigSettings() s.load_file('/tmp/foo.ini')
def test_assignment_validation(self): s = ConfigSettings() s.register_provider(Provider2) a = s.a # Assigning an undeclared setting raises. with self.assertRaises(KeyError): a.undefined = True with self.assertRaises(KeyError): a['undefined'] = True # Basic type validation. a.string = 'foo' a.string = 'foo' with self.assertRaises(TypeError): a.string = False a.boolean = True a.boolean = False with self.assertRaises(TypeError): a.boolean = 'foo' a.pos_int = 5 a.pos_int = 0 with self.assertRaises(ValueError): a.pos_int = -1 with self.assertRaises(TypeError): a.pos_int = 'foo' a.int = 5 a.int = 0 a.int = -5 with self.assertRaises(TypeError): a.int = 1.24 with self.assertRaises(TypeError): a.int = 'foo' a.abs_path = '/home/gps' with self.assertRaises(ValueError): a.abs_path = 'home/gps' a.rel_path = 'home/gps' a.rel_path = './foo/bar' a.rel_path = 'foo.c' with self.assertRaises(ValueError): a.rel_path = '/foo/bar' a.path = '/home/gps' a.path = 'foo.c' a.path = 'foo/bar' a.path = './foo'