Example #1
0
  def setUp(self):
    super(BaseTest, self).setUp()
    Goal.clear()
    self.real_build_root = BuildRoot().path
    self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
    self.pants_workdir = os.path.join(self.build_root, '.pants.d')
    safe_mkdir(self.pants_workdir)
    self.options = defaultdict(dict)  # scope -> key-value mapping.
    self.options[''] = {
      'pants_workdir': self.pants_workdir,
      'pants_supportdir': os.path.join(self.build_root, 'build-support'),
      'pants_distdir': os.path.join(self.build_root, 'dist'),
      'pants_configdir': os.path.join(self.build_root, 'config'),
      'cache_key_gen_version': '0-test',
    }
    BuildRoot().path = self.build_root
    Subsystem.reset()

    self.create_file('pants.ini')
    build_configuration = BuildConfiguration()
    build_configuration.register_aliases(self.alias_groups)
    self.build_file_parser = BuildFileParser(build_configuration, self.build_root)
    self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile)
    self.build_graph = BuildGraph(address_mapper=self.address_mapper)
    self.bootstrap_option_values = OptionsBootstrapper().get_bootstrap_options().for_global_scope()
Example #2
0
  def setUp(self):
    Goal.clear()
    self.real_build_root = BuildRoot().path
    self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
    BuildRoot().path = self.build_root

    self.create_file('pants.ini')
    build_configuration = BuildConfiguration()
    build_configuration.register_aliases(self.alias_groups)
    self.build_file_parser = BuildFileParser(build_configuration, self.build_root)
    self.address_mapper = BuildFileAddressMapper(self.build_file_parser)
    self.build_graph = BuildGraph(address_mapper=self.address_mapper)
Example #3
0
    def setUp(self):
        Goal.clear()
        self.real_build_root = BuildRoot().path
        self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
        BuildRoot().path = self.build_root

        self.create_file('pants.ini')
        build_configuration = BuildConfiguration()
        build_configuration.register_aliases(self.alias_groups)
        self.build_file_parser = BuildFileParser(build_configuration,
                                                 self.build_root)
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)
Example #4
0
  def setUp(self):
    super(BaseTest, self).setUp()
    Goal.clear()
    self.real_build_root = BuildRoot().path
    self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
    self.new_options = defaultdict(dict)  # scope -> key-value mapping.
    BuildRoot().path = self.build_root

    self.create_file('pants.ini')
    build_configuration = BuildConfiguration()
    build_configuration.register_aliases(self.alias_groups)
    self.build_file_parser = BuildFileParser(build_configuration, self.build_root)
    self.address_mapper = BuildFileAddressMapper(self.build_file_parser)
    self.build_graph = BuildGraph(address_mapper=self.address_mapper)
Example #5
0
    def setUp(self):
        super(BaseTest, self).setUp()
        Goal.clear()
        self.real_build_root = BuildRoot().path
        self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
        self.new_options = defaultdict(dict)  # scope -> key-value mapping.
        self.new_options[''] = {
            'pants_workdir': os.path.join(self.build_root, '.pants.d'),
            'pants_supportdir': os.path.join(self.build_root, 'build-support'),
            'pants_distdir': os.path.join(self.build_root, 'dist')
        }
        BuildRoot().path = self.build_root

        self.create_file('pants.ini')
        build_configuration = BuildConfiguration()
        build_configuration.register_aliases(self.alias_groups)
        self.build_file_parser = BuildFileParser(build_configuration,
                                                 self.build_root)
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)
Example #6
0
class BaseTest(unittest.TestCase):
  """A baseclass useful for tests requiring a temporary buildroot."""

  def build_path(self, relpath):
    """Returns the canonical BUILD file path for the given relative build path."""
    if os.path.basename(relpath).startswith('BUILD'):
      return relpath
    else:
      return os.path.join(relpath, 'BUILD')

  def create_dir(self, relpath):
    """Creates a directory under the buildroot.

    relpath: The relative path to the directory from the build root.
    """
    path = os.path.join(self.build_root, relpath)
    safe_mkdir(path)
    return path

  def create_file(self, relpath, contents='', mode='wb'):
    """Writes to a file under the buildroot.

    relpath:  The relative path to the file from the build root.
    contents: A string containing the contents of the file - '' by default..
    mode:     The mode to write to the file in - over-write by default.
    """
    path = os.path.join(self.build_root, relpath)
    with safe_open(path, mode=mode) as fp:
      fp.write(contents)
    return path

  def add_to_build_file(self, relpath, target):
    """Adds the given target specification to the BUILD file at relpath.

    relpath: The relative path to the BUILD file from the build root.
    target:  A string containing the target definition as it would appear in a BUILD file.
    """
    self.create_file(self.build_path(relpath), target, mode='a')
    cls = self.address_mapper._build_file_type
    return cls(root_dir=self.build_root, relpath=self.build_path(relpath))

  def make_target(self,
                  spec='',
                  target_type=Target,
                  dependencies=None,
                  resources = None,
                  derived_from=None,
                  **kwargs):
    address = SyntheticAddress.parse(spec)
    target = target_type(name=address.target_name,
                         address=address,
                         build_graph=self.build_graph,
                         **kwargs)
    dependencies = dependencies or []
    dependencies.extend(resources or [])

    self.build_graph.inject_target(target,
                                   dependencies=[dep.address for dep in dependencies],
                                   derived_from=derived_from)
    return target

  @property
  def alias_groups(self):
    return BuildFileAliases.create(targets={'target': Dependencies})

  def setUp(self):
    super(BaseTest, self).setUp()
    Goal.clear()
    Subsystem.reset()

    self.real_build_root = BuildRoot().path

    self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
    self.addCleanup(safe_rmtree, self.build_root)

    self.pants_workdir = os.path.join(self.build_root, '.pants.d')
    safe_mkdir(self.pants_workdir)

    self.options = defaultdict(dict)  # scope -> key-value mapping.
    self.options[''] = {
      'pants_workdir': self.pants_workdir,
      'pants_supportdir': os.path.join(self.build_root, 'build-support'),
      'pants_distdir': os.path.join(self.build_root, 'dist'),
      'pants_configdir': os.path.join(self.build_root, 'config'),
      'cache_key_gen_version': '0-test',
    }

    BuildRoot().path = self.build_root
    self.addCleanup(BuildRoot().reset)

    # We need a pants.ini, even if empty. get_buildroot() uses its presence.
    self.create_file('pants.ini')
    self._build_configuration = BuildConfiguration()
    self._build_configuration.register_aliases(self.alias_groups)
    self.build_file_parser = BuildFileParser(self._build_configuration, self.build_root)
    self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile)
    self.build_graph = BuildGraph(address_mapper=self.address_mapper)

  def reset_build_graph(self):
    """Start over with a fresh build graph with no targets in it."""
    self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile)
    self.build_graph = BuildGraph(address_mapper=self.address_mapper)

  def set_options_for_scope(self, scope, **kwargs):
    self.options[scope].update(kwargs)

  def context(self, for_task_types=None, options=None, target_roots=None,
              console_outstream=None, workspace=None):
    for_task_types = for_task_types or []
    options = options or {}

    option_values = defaultdict(dict)
    registered_global_subsystems = set()
    bootstrap_option_values = None  # We fill these in after registering bootstrap options.

    # We provide our own test-only registration implementation, bypassing argparse.
    # When testing we set option values directly, so we don't care about cmd-line flags, config,
    # env vars etc. In fact, for test isolation we explicitly don't want to look at those.
    # All this does is make the names available in code, with the default values.
    # Individual tests can then override the option values they care about.
    def register_func(on_scope):
      def register(*rargs, **rkwargs):
        scoped_options = option_values[on_scope]
        default = rkwargs.get('default')
        if default is None and rkwargs.get('action') == 'append':
          default = []
        for flag_name in rargs:
          option_name = flag_name.lstrip('-').replace('-', '_')
          scoped_options[option_name] = default
      register.bootstrap = bootstrap_option_values
      register.scope = on_scope
      return register

    # TODO: This sequence is a bit repetitive of the real registration sequence.

    # Register bootstrap options and grab their default values for use in subsequent registration.
    register_bootstrap_options(register_func(Options.GLOBAL_SCOPE), self.build_root)
    bootstrap_option_values = create_option_values(copy.copy(option_values[Options.GLOBAL_SCOPE]))

    # Now register the remaining global scope options.
    register_global_options(register_func(Options.GLOBAL_SCOPE))

    # Now register task and subsystem options for relevant tasks.
    for task_type in for_task_types:
      scope = task_type.options_scope
      if scope is None:
        raise TaskError('You must set a scope on your task type before using it in tests.')
      task_type.register_options(register_func(scope))
      for subsystem in (set(task_type.global_subsystems()) |
                        self._build_configuration.subsystem_types()):
        if subsystem not in registered_global_subsystems:
          subsystem.register_options(register_func(subsystem.qualify_scope(Options.GLOBAL_SCOPE)))
          registered_global_subsystems.add(subsystem)
      for subsystem in task_type.task_subsystems():
        subsystem.register_options(register_func(subsystem.qualify_scope(scope)))

    # Now default option values override with any caller-specified values.
    # TODO(benjy): Get rid of the options arg, and require tests to call set_options.

    for scope, opts in options.items():
      for key, val in opts.items():
        option_values[scope][key] = val

    for scope, opts in self.options.items():
      for key, val in opts.items():
        option_values[scope][key] = val

    # Make inner scopes inherit option values from their enclosing scopes.
    # Iterating in sorted order guarantees that we see outer scopes before inner scopes,
    # and therefore only have to inherit from our immediately enclosing scope.
    for scope in sorted(option_values.keys()):
      if scope != Options.GLOBAL_SCOPE:
        enclosing_scope = scope.rpartition('.')[0]
        opts = option_values[scope]
        for key, val in option_values.get(enclosing_scope, {}).items():
          if key not in opts:  # Inner scope values override the inherited ones.
            opts[key] = val

    context = create_context(options=option_values,
                             target_roots=target_roots,
                             build_graph=self.build_graph,
                             build_file_parser=self.build_file_parser,
                             address_mapper=self.address_mapper,
                             console_outstream=console_outstream,
                             workspace=workspace)
    Subsystem._options = context.options
    return context

  def tearDown(self):
    SourceRoot.reset()
    FilesystemBuildFile.clear_cache()

  def target(self, spec):
    """Resolves the given target address to a Target object.

    address: The BUILD target address to resolve.

    Returns the corresponding Target or else None if the address does not point to a defined Target.
    """
    address = SyntheticAddress.parse(spec)
    self.build_graph.inject_address_closure(address)
    return self.build_graph.get_target(address)

  def targets(self, spec):
    """Resolves a target spec to one or more Target objects.

    spec: Either BUILD target address or else a target glob using the siblings ':' or
          descendants '::' suffixes.

    Returns the set of all Targets found.
    """

    spec_parser = CmdLineSpecParser(self.build_root, self.address_mapper)
    addresses = list(spec_parser.parse_addresses(spec))
    for address in addresses:
      self.build_graph.inject_address_closure(address)
    targets = [self.build_graph.get_target(address) for address in addresses]
    return targets

  def create_files(self, path, files):
    """Writes to a file under the buildroot with contents same as file name.

     path:  The relative path to the file from the build root.
     files: List of file names.
    """
    for f in files:
      self.create_file(os.path.join(path, f), contents=f)

  def create_library(self, path, target_type, name, sources=None, **kwargs):
    """Creates a library target of given type at the BUILD file at path with sources

     path: The relative path to the BUILD file from the build root.
     target_type: valid pants target type.
     name: Name of the library target.
     sources: List of source file at the path relative to path.
     **kwargs: Optional attributes that can be set for any library target.
       Currently it includes support for resources, java_sources, provides
       and dependencies.
    """
    if sources:
      self.create_files(path, sources)
    self.add_to_build_file(path, dedent('''
          %(target_type)s(name='%(name)s',
            %(sources)s
            %(resources)s
            %(java_sources)s
            %(provides)s
            %(dependencies)s
          )
        ''' % dict(target_type=target_type,
                   name=name,
                   sources=('sources=%s,' % repr(sources)
                              if sources else ''),
                   resources=('resources=["%s"],' % kwargs.get('resources')
                              if 'resources' in kwargs else ''),
                   java_sources=('java_sources=[%s],'
                                 % ','.join(map(lambda str_target: '"%s"' % str_target,
                                                kwargs.get('java_sources')))
                                 if 'java_sources' in kwargs else ''),
                   provides=('provides=%s,' % kwargs.get('provides')
                              if 'provides' in kwargs else ''),
                   dependencies=('dependencies=%s,' % kwargs.get('dependencies')
                              if 'dependencies' in kwargs else ''),
                   )))
    return self.target('%s:%s' % (path, name))

  def create_resources(self, path, name, *sources):
    return self.create_library(path, 'resources', name, sources)

  @contextmanager
  def workspace(self, *buildfiles):
    with temporary_dir() as root_dir:
      with BuildRoot().temporary(root_dir):
        with pushd(root_dir):
          for buildfile in buildfiles:
            touch(os.path.join(root_dir, buildfile))
          yield os.path.realpath(root_dir)

  def populate_compile_classpath(self, context, classpath=None):
    """
    Helps actual test cases to populate the 'compile_classpath' products data mapping
    in the context, which holds the classpath value for targets.

    :param context: The execution context where the products data mapping lives.
    :param classpath: a list of classpath strings. If not specified, [os.path.join(self.buildroot, 'none')] will be used.
    """
    classpath = classpath or [os.path.join(self.build_root, 'none')]
    compile_classpaths = context.products.get_data('compile_classpath', lambda: UnionProducts())
    compile_classpaths.add_for_targets(context.targets(), [('default', entry) for entry in classpath])

  @contextmanager
  def add_data(self, context_products, data_type, target, *products):
    make_products = lambda: defaultdict(MultipleRootedProducts)
    data_by_target = context_products.get_data(data_type, make_products)
    with temporary_dir() as outdir:
      def create_product(product):
        abspath = os.path.join(outdir, product)
        with safe_open(abspath, mode='w') as fp:
          fp.write(product)
        return abspath
      data_by_target[target].add_abs_paths(outdir, map(create_product, products))
      yield temporary_dir

  @contextmanager
  def add_products(self, context_products, product_type, target, *products):
    product_mapping = context_products.get(product_type)
    with temporary_dir() as outdir:
      def create_product(product):
        with safe_open(os.path.join(outdir, product), mode='w') as fp:
          fp.write(product)
        return product
      product_mapping.add(target, outdir, map(create_product, products))
      yield temporary_dir
Example #7
0
class BuildConfigurationTest(unittest.TestCase):
  def setUp(self):
    self.build_configuration = BuildConfiguration()

  def _register_aliases(self, **kwargs):
    self.build_configuration.register_aliases(BuildFileAliases(**kwargs))

  def test_register_bad(self):
    with self.assertRaises(TypeError):
      self.build_configuration.register_aliases(42)

  def test_register_target_alias(self):
    class Fred(Target):
      pass

    self._register_aliases(targets={'fred': Fred})
    aliases = self.build_configuration.registered_aliases()
    self.assertEqual({}, aliases.target_macro_factories)
    self.assertEqual({}, aliases.objects)
    self.assertEqual({}, aliases.context_aware_object_factories)
    self.assertEqual(dict(fred=Fred), aliases.target_types)

    build_file = FilesystemBuildFile('/tmp', 'fred', must_exist=False)
    parse_state = self.build_configuration.initialize_parse_state(build_file)

    self.assertEqual(0, len(parse_state.registered_addressable_instances))
    self.assertEqual(1, len(parse_state.parse_globals))

    target_call_proxy = parse_state.parse_globals['fred']
    target_call_proxy(name='jake')

    self.assertEqual(1, len(parse_state.registered_addressable_instances))
    name, target_proxy = parse_state.registered_addressable_instances.pop()
    self.assertEqual('jake', target_proxy.addressed_name)
    self.assertEqual(Fred, target_proxy.addressed_type)

  def test_register_target_macro_facory(self):
    class Fred(Target):
      pass

    class FredMacro(TargetMacro):
      def __init__(self, parse_context):
        self._parse_context = parse_context

      def expand(self, *args, **kwargs):
        return self._parse_context.create_object(Fred, name='frog', dependencies=[kwargs['name']])

    class FredFactory(TargetMacro.Factory):
      @property
      def target_types(self):
        return {Fred}

      def macro(self, parse_context):
        return FredMacro(parse_context)

    factory = FredFactory()

    self._register_aliases(targets={'fred': factory})
    aliases = self.build_configuration.registered_aliases()
    self.assertEqual({}, aliases.target_types)
    self.assertEqual({}, aliases.objects)
    self.assertEqual({}, aliases.context_aware_object_factories)
    self.assertEqual(dict(fred=factory), aliases.target_macro_factories)

    build_file = FilesystemBuildFile('/tmp', 'fred', must_exist=False)
    parse_state = self.build_configuration.initialize_parse_state(build_file)

    self.assertEqual(0, len(parse_state.registered_addressable_instances))
    self.assertEqual(1, len(parse_state.parse_globals))

    target_call_proxy = parse_state.parse_globals['fred']
    target_call_proxy(name='jake')

    self.assertEqual(1, len(parse_state.registered_addressable_instances))
    name, target_proxy = parse_state.registered_addressable_instances.pop()
    self.assertEqual('frog', target_proxy.addressed_name)
    self.assertEqual(Fred, target_proxy.addressed_type)
    self.assertEqual(['jake'], target_proxy.dependency_specs)

  def test_register_exposed_object(self):
    self._register_aliases(objects={'jane': 42})

    aliases = self.build_configuration.registered_aliases()
    self.assertEqual({}, aliases.target_types)
    self.assertEqual({}, aliases.target_macro_factories)
    self.assertEqual({}, aliases.context_aware_object_factories)
    self.assertEqual(dict(jane=42), aliases.objects)

    build_file = FilesystemBuildFile('/tmp', 'jane', must_exist=False)
    parse_state = self.build_configuration.initialize_parse_state(build_file)

    self.assertEqual(0, len(parse_state.registered_addressable_instances))
    self.assertEqual(1, len(parse_state.parse_globals))
    self.assertEqual(42, parse_state.parse_globals['jane'])

  def test_register_exposed_context_aware_function(self):
    self.do_test_exposed_context_aware_function(lambda context: lambda: context.rel_path)
    self.do_test_exposed_context_aware_function(lambda context=None: lambda: context.rel_path)

  def george_method(self, parse_context):
    return lambda: parse_context.rel_path

  def test_register_exposed_context_aware_method(self):
    self.do_test_exposed_context_aware_function(self.george_method)

  @classmethod
  def george_classmethod(cls, parse_context):
    return lambda: parse_context.rel_path

  def test_register_exposed_context_aware_classmethod(self):
    self.do_test_exposed_context_aware_function(self.george_classmethod)

  @staticmethod
  def george_staticmethod(parse_context):
    return lambda: parse_context.rel_path

  def test_register_exposed_context_aware_staticmethod(self):
    self.do_test_exposed_context_aware_function(self.george_staticmethod)

  def do_test_exposed_context_aware_function(self, func, *args, **kwargs):
    with self.do_test_exposed_context_aware_object(func) as context_aware_object:
      self.assertEqual('george', context_aware_object(*args, **kwargs))

  def test_register_exposed_context_aware_class(self):
    class George(object):
      def __init__(self, parse_context):
        self._parse_context = parse_context

      def honorific(self):
        return len(self._parse_context.rel_path)

    with self.do_test_exposed_context_aware_object(George) as context_aware_object:
      self.assertEqual(6, context_aware_object.honorific())

  @contextmanager
  def do_test_exposed_context_aware_object(self, context_aware_object_factory):
    self._register_aliases(context_aware_object_factories={'george': context_aware_object_factory})

    aliases = self.build_configuration.registered_aliases()
    self.assertEqual({}, aliases.target_types)
    self.assertEqual({}, aliases.target_macro_factories)
    self.assertEqual({}, aliases.objects)
    self.assertEqual(dict(george=context_aware_object_factory),
                     aliases.context_aware_object_factories)

    with temporary_dir() as root:
      build_file_path = os.path.join(root, 'george', 'BUILD')
      touch(build_file_path)
      build_file = FilesystemBuildFile(root, 'george')
      parse_state = self.build_configuration.initialize_parse_state(build_file)

      self.assertEqual(0, len(parse_state.registered_addressable_instances))
      self.assertEqual(1, len(parse_state.parse_globals))
      yield parse_state.parse_globals['george']
Example #8
0
class BaseTest(unittest.TestCase):
  """A baseclass useful for tests requiring a temporary buildroot."""

  def build_path(self, relpath):
    """Returns the canonical BUILD file path for the given relative build path."""
    if os.path.basename(relpath).startswith('BUILD'):
      return relpath
    else:
      return os.path.join(relpath, 'BUILD')

  def create_dir(self, relpath):
    """Creates a directory under the buildroot.

    relpath: The relative path to the directory from the build root.
    """
    path = os.path.join(self.build_root, relpath)
    safe_mkdir(path)
    return path

  def create_file(self, relpath, contents='', mode='wb'):
    """Writes to a file under the buildroot.

    relpath:  The relative path to the file from the build root.
    contents: A string containing the contents of the file - '' by default..
    mode:     The mode to write to the file in - over-write by default.
    """
    path = os.path.join(self.build_root, relpath)
    with safe_open(path, mode=mode) as fp:
      fp.write(contents)
    return path

  def add_to_build_file(self, relpath, target):
    """Adds the given target specification to the BUILD file at relpath.

    relpath: The relative path to the BUILD file from the build root.
    target:  A string containing the target definition as it would appear in a BUILD file.
    """
    self.create_file(self.build_path(relpath), target, mode='a')
    cls = self.address_mapper._build_file_type
    return cls(root_dir=self.build_root, relpath=self.build_path(relpath))

  def make_target(self,
                  spec='',
                  target_type=Target,
                  dependencies=None,
                  resources=None,
                  derived_from=None,
                  **kwargs):
    address = SyntheticAddress.parse(spec)
    target = target_type(name=address.target_name,
                         address=address,
                         build_graph=self.build_graph,
                         **kwargs)
    dependencies = dependencies or []
    dependencies.extend(resources or [])

    self.build_graph.inject_target(target,
                                   dependencies=[dep.address for dep in dependencies],
                                   derived_from=derived_from)
    return target

  @property
  def alias_groups(self):
    return BuildFileAliases.create(targets={'target': Dependencies})

  def setUp(self):
    super(BaseTest, self).setUp()
    Goal.clear()
    Subsystem.reset()

    self.real_build_root = BuildRoot().path

    self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
    self.addCleanup(safe_rmtree, self.build_root)

    self.pants_workdir = os.path.join(self.build_root, '.pants.d')
    safe_mkdir(self.pants_workdir)

    self.options = defaultdict(dict)  # scope -> key-value mapping.
    self.options[''] = {
      'pants_workdir': self.pants_workdir,
      'pants_supportdir': os.path.join(self.build_root, 'build-support'),
      'pants_distdir': os.path.join(self.build_root, 'dist'),
      'pants_configdir': os.path.join(self.build_root, 'config'),
      'cache_key_gen_version': '0-test',
    }

    BuildRoot().path = self.build_root
    self.addCleanup(BuildRoot().reset)

    # We need a pants.ini, even if empty. get_buildroot() uses its presence.
    self.create_file('pants.ini')
    self._build_configuration = BuildConfiguration()
    self._build_configuration.register_aliases(self.alias_groups)
    self.build_file_parser = BuildFileParser(self._build_configuration, self.build_root)
    self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile)
    self.build_graph = BuildGraph(address_mapper=self.address_mapper)

  def reset_build_graph(self):
    """Start over with a fresh build graph with no targets in it."""
    self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile)
    self.build_graph = BuildGraph(address_mapper=self.address_mapper)

  def set_options_for_scope(self, scope, **kwargs):
    self.options[scope].update(kwargs)

  def context(self, for_task_types=None, options=None, target_roots=None, console_outstream=None,
              workspace=None, for_subsystems=None):

    optionables = set()
    extra_scopes = set()

    for_subsystems = for_subsystems or ()
    for subsystem in for_subsystems:
      if subsystem.options_scope is None:
        raise TaskError('You must set a scope on your subsystem type before using it in tests.')
      optionables.add(subsystem)

    for_task_types = for_task_types or ()
    for task_type in for_task_types:
      scope = task_type.options_scope
      if scope is None:
        raise TaskError('You must set a scope on your task type before using it in tests.')
      optionables.add(task_type)
      extra_scopes.update([si.scope for si in task_type.known_scope_infos()])
      optionables.update(Subsystem.closure(
        set([dep.subsystem_cls for dep in task_type.subsystem_dependencies_iter()]) |
            self._build_configuration.subsystems()))

    # Now default the option values and override with any caller-specified values.
    # TODO(benjy): Get rid of the options arg, and require tests to call set_options.
    options = options.copy() if options else {}
    for s, opts in self.options.items():
      scoped_opts = options.setdefault(s, {})
      scoped_opts.update(opts)

    option_values = create_options_for_optionables(optionables,
                                                   extra_scopes=extra_scopes,
                                                   options=options)

    context = create_context(options=option_values,
                             target_roots=target_roots,
                             build_graph=self.build_graph,
                             build_file_parser=self.build_file_parser,
                             address_mapper=self.address_mapper,
                             console_outstream=console_outstream,
                             workspace=workspace)
    Subsystem._options = context.options
    return context

  def tearDown(self):
    SourceRoot.reset()
    FilesystemBuildFile.clear_cache()
    Subsystem.reset()

  def target(self, spec):
    """Resolves the given target address to a Target object.

    address: The BUILD target address to resolve.

    Returns the corresponding Target or else None if the address does not point to a defined Target.
    """
    address = SyntheticAddress.parse(spec)
    self.build_graph.inject_address_closure(address)
    return self.build_graph.get_target(address)

  def targets(self, spec):
    """Resolves a target spec to one or more Target objects.

    spec: Either BUILD target address or else a target glob using the siblings ':' or
          descendants '::' suffixes.

    Returns the set of all Targets found.
    """

    spec_parser = CmdLineSpecParser(self.build_root, self.address_mapper)
    addresses = list(spec_parser.parse_addresses(spec))
    for address in addresses:
      self.build_graph.inject_address_closure(address)
    targets = [self.build_graph.get_target(address) for address in addresses]
    return targets

  def create_files(self, path, files):
    """Writes to a file under the buildroot with contents same as file name.

     path:  The relative path to the file from the build root.
     files: List of file names.
    """
    for f in files:
      self.create_file(os.path.join(path, f), contents=f)

  def create_library(self, path, target_type, name, sources=None, **kwargs):
    """Creates a library target of given type at the BUILD file at path with sources

     path: The relative path to the BUILD file from the build root.
     target_type: valid pants target type.
     name: Name of the library target.
     sources: List of source file at the path relative to path.
     **kwargs: Optional attributes that can be set for any library target.
       Currently it includes support for resources, java_sources, provides
       and dependencies.
    """
    if sources:
      self.create_files(path, sources)
    self.add_to_build_file(path, dedent('''
          %(target_type)s(name='%(name)s',
            %(sources)s
            %(resources)s
            %(java_sources)s
            %(provides)s
            %(dependencies)s
          )
        ''' % dict(target_type=target_type,
                   name=name,
                   sources=('sources=%s,' % repr(sources)
                              if sources else ''),
                   resources=('resources=["%s"],' % kwargs.get('resources')
                              if 'resources' in kwargs else ''),
                   java_sources=('java_sources=[%s],'
                                 % ','.join(map(lambda str_target: '"%s"' % str_target,
                                                kwargs.get('java_sources')))
                                 if 'java_sources' in kwargs else ''),
                   provides=('provides=%s,' % kwargs.get('provides')
                              if 'provides' in kwargs else ''),
                   dependencies=('dependencies=%s,' % kwargs.get('dependencies')
                              if 'dependencies' in kwargs else ''),
                   )))
    return self.target('%s:%s' % (path, name))

  def create_resources(self, path, name, *sources):
    return self.create_library(path, 'resources', name, sources)

  @contextmanager
  def workspace(self, *buildfiles):
    with temporary_dir() as root_dir:
      with BuildRoot().temporary(root_dir):
        with pushd(root_dir):
          for buildfile in buildfiles:
            touch(os.path.join(root_dir, buildfile))
          yield os.path.realpath(root_dir)

  def populate_compile_classpath(self, context, classpath=None):
    """
    Helps actual test cases to populate the 'compile_classpath' products data mapping
    in the context, which holds the classpath value for targets.

    :param context: The execution context where the products data mapping lives.
    :param classpath: a list of classpath strings. If not specified,
                      [os.path.join(self.buildroot, 'none')] will be used.
    """
    classpath = classpath or [os.path.join(self.build_root, 'none')]
    compile_classpaths = context.products.get_data('compile_classpath', lambda: UnionProducts())
    compile_classpaths.add_for_targets(context.targets(),
                                       [('default', entry) for entry in classpath])

  @contextmanager
  def add_data(self, context_products, data_type, target, *products):
    make_products = lambda: defaultdict(MultipleRootedProducts)
    data_by_target = context_products.get_data(data_type, make_products)
    with temporary_dir() as outdir:
      def create_product(product):
        abspath = os.path.join(outdir, product)
        with safe_open(abspath, mode='w') as fp:
          fp.write(product)
        return abspath
      data_by_target[target].add_abs_paths(outdir, map(create_product, products))
      yield temporary_dir

  @contextmanager
  def add_products(self, context_products, product_type, target, *products):
    product_mapping = context_products.get(product_type)
    with temporary_dir() as outdir:
      def create_product(product):
        with safe_open(os.path.join(outdir, product), mode='w') as fp:
          fp.write(product)
        return product
      product_mapping.add(target, outdir, map(create_product, products))
      yield temporary_dir
Example #9
0
class BaseTest(unittest.TestCase):
    """A baseclass useful for tests requiring a temporary buildroot."""
    def build_path(self, relpath):
        """Returns the canonical BUILD file path for the given relative build path."""
        if os.path.basename(relpath).startswith('BUILD'):
            return relpath
        else:
            return os.path.join(relpath, 'BUILD')

    def create_dir(self, relpath):
        """Creates a directory under the buildroot.

    relpath: The relative path to the directory from the build root.
    """
        path = os.path.join(self.build_root, relpath)
        safe_mkdir(path)
        return path

    def create_file(self, relpath, contents='', mode='wb'):
        """Writes to a file under the buildroot.

    relpath:  The relative path to the file from the build root.
    contents: A string containing the contents of the file - '' by default..
    mode:     The mode to write to the file in - over-write by default.
    """
        path = os.path.join(self.build_root, relpath)
        with safe_open(path, mode=mode) as fp:
            fp.write(contents)
        return path

    def add_to_build_file(self, relpath, target):
        """Adds the given target specification to the BUILD file at relpath.

    relpath: The relative path to the BUILD file from the build root.
    target:  A string containing the target definition as it would appear in a BUILD file.
    """
        self.create_file(self.build_path(relpath), target, mode='a')
        cls = self.address_mapper._build_file_type
        return cls(root_dir=self.build_root, relpath=self.build_path(relpath))

    def make_target(self,
                    spec='',
                    target_type=Target,
                    dependencies=None,
                    resources=None,
                    derived_from=None,
                    **kwargs):
        address = SyntheticAddress.parse(spec)
        target = target_type(name=address.target_name,
                             address=address,
                             build_graph=self.build_graph,
                             **kwargs)
        dependencies = dependencies or []
        dependencies.extend(resources or [])

        self.build_graph.inject_target(
            target,
            dependencies=[dep.address for dep in dependencies],
            derived_from=derived_from)
        return target

    @property
    def alias_groups(self):
        return BuildFileAliases.create(targets={'target': Dependencies})

    def setUp(self):
        super(BaseTest, self).setUp()
        Goal.clear()
        Subsystem.reset()

        self.real_build_root = BuildRoot().path

        self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
        self.addCleanup(safe_rmtree, self.build_root)

        self.pants_workdir = os.path.join(self.build_root, '.pants.d')
        safe_mkdir(self.pants_workdir)

        self.options = defaultdict(dict)  # scope -> key-value mapping.
        self.options[''] = {
            'pants_workdir': self.pants_workdir,
            'pants_supportdir': os.path.join(self.build_root, 'build-support'),
            'pants_distdir': os.path.join(self.build_root, 'dist'),
            'pants_configdir': os.path.join(self.build_root, 'config'),
            'cache_key_gen_version': '0-test',
        }

        BuildRoot().path = self.build_root
        self.addCleanup(BuildRoot().reset)

        # We need a pants.ini, even if empty. get_buildroot() uses its presence.
        self.create_file('pants.ini')
        self._build_configuration = BuildConfiguration()
        self._build_configuration.register_aliases(self.alias_groups)
        self.build_file_parser = BuildFileParser(self._build_configuration,
                                                 self.build_root)
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser,
                                                     FilesystemBuildFile)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)

    def reset_build_graph(self):
        """Start over with a fresh build graph with no targets in it."""
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser,
                                                     FilesystemBuildFile)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)

    def set_options_for_scope(self, scope, **kwargs):
        self.options[scope].update(kwargs)

    def context(self,
                for_task_types=None,
                options=None,
                target_roots=None,
                console_outstream=None,
                workspace=None,
                for_subsystems=None):

        optionables = set()
        extra_scopes = set()

        for_subsystems = for_subsystems or ()
        for subsystem in for_subsystems:
            if subsystem.options_scope is None:
                raise TaskError(
                    'You must set a scope on your subsystem type before using it in tests.'
                )
            optionables.add(subsystem)

        for_task_types = for_task_types or ()
        for task_type in for_task_types:
            scope = task_type.options_scope
            if scope is None:
                raise TaskError(
                    'You must set a scope on your task type before using it in tests.'
                )
            optionables.add(task_type)
            extra_scopes.update(
                [si.scope for si in task_type.known_scope_infos()])
            optionables.update(
                Subsystem.closure(
                    set([
                        dep.subsystem_cls
                        for dep in task_type.subsystem_dependencies_iter()
                    ]) | self._build_configuration.subsystems()))

        # Now default the option values and override with any caller-specified values.
        # TODO(benjy): Get rid of the options arg, and require tests to call set_options.
        options = options.copy() if options else {}
        for s, opts in self.options.items():
            scoped_opts = options.setdefault(s, {})
            scoped_opts.update(opts)

        option_values = create_options_for_optionables(
            optionables, extra_scopes=extra_scopes, options=options)

        context = create_context(options=option_values,
                                 target_roots=target_roots,
                                 build_graph=self.build_graph,
                                 build_file_parser=self.build_file_parser,
                                 address_mapper=self.address_mapper,
                                 console_outstream=console_outstream,
                                 workspace=workspace)
        Subsystem._options = context.options
        return context

    def tearDown(self):
        SourceRoot.reset()
        FilesystemBuildFile.clear_cache()
        Subsystem.reset()

    def target(self, spec):
        """Resolves the given target address to a Target object.

    address: The BUILD target address to resolve.

    Returns the corresponding Target or else None if the address does not point to a defined Target.
    """
        address = SyntheticAddress.parse(spec)
        self.build_graph.inject_address_closure(address)
        return self.build_graph.get_target(address)

    def targets(self, spec):
        """Resolves a target spec to one or more Target objects.

    spec: Either BUILD target address or else a target glob using the siblings ':' or
          descendants '::' suffixes.

    Returns the set of all Targets found.
    """

        spec_parser = CmdLineSpecParser(self.build_root, self.address_mapper)
        addresses = list(spec_parser.parse_addresses(spec))
        for address in addresses:
            self.build_graph.inject_address_closure(address)
        targets = [
            self.build_graph.get_target(address) for address in addresses
        ]
        return targets

    def create_files(self, path, files):
        """Writes to a file under the buildroot with contents same as file name.

     path:  The relative path to the file from the build root.
     files: List of file names.
    """
        for f in files:
            self.create_file(os.path.join(path, f), contents=f)

    def create_library(self, path, target_type, name, sources=None, **kwargs):
        """Creates a library target of given type at the BUILD file at path with sources

     path: The relative path to the BUILD file from the build root.
     target_type: valid pants target type.
     name: Name of the library target.
     sources: List of source file at the path relative to path.
     **kwargs: Optional attributes that can be set for any library target.
       Currently it includes support for resources, java_sources, provides
       and dependencies.
    """
        if sources:
            self.create_files(path, sources)
        self.add_to_build_file(
            path,
            dedent('''
          %(target_type)s(name='%(name)s',
            %(sources)s
            %(resources)s
            %(java_sources)s
            %(provides)s
            %(dependencies)s
          )
        ''' % dict(
                target_type=target_type,
                name=name,
                sources=('sources=%s,' % repr(sources) if sources else ''),
                resources=('resources=["%s"],' % kwargs.get('resources')
                           if 'resources' in kwargs else ''),
                java_sources=('java_sources=[%s],' % ','.join(
                    map(lambda str_target: '"%s"' % str_target,
                        kwargs.get('java_sources')))
                              if 'java_sources' in kwargs else ''),
                provides=('provides=%s,' % kwargs.get('provides')
                          if 'provides' in kwargs else ''),
                dependencies=('dependencies=%s,' % kwargs.get('dependencies')
                              if 'dependencies' in kwargs else ''),
            )))
        return self.target('%s:%s' % (path, name))

    def create_resources(self, path, name, *sources):
        return self.create_library(path, 'resources', name, sources)

    @contextmanager
    def workspace(self, *buildfiles):
        with temporary_dir() as root_dir:
            with BuildRoot().temporary(root_dir):
                with pushd(root_dir):
                    for buildfile in buildfiles:
                        touch(os.path.join(root_dir, buildfile))
                    yield os.path.realpath(root_dir)

    def populate_compile_classpath(self, context, classpath=None):
        """
    Helps actual test cases to populate the 'compile_classpath' products data mapping
    in the context, which holds the classpath value for targets.

    :param context: The execution context where the products data mapping lives.
    :param classpath: a list of classpath strings. If not specified,
                      [os.path.join(self.buildroot, 'none')] will be used.
    """
        classpath = classpath or [os.path.join(self.build_root, 'none')]
        compile_classpaths = context.products.get_data('compile_classpath',
                                                       lambda: UnionProducts())
        compile_classpaths.add_for_targets(context.targets(),
                                           [('default', entry)
                                            for entry in classpath])

    @contextmanager
    def add_data(self, context_products, data_type, target, *products):
        make_products = lambda: defaultdict(MultipleRootedProducts)
        data_by_target = context_products.get_data(data_type, make_products)
        with temporary_dir() as outdir:

            def create_product(product):
                abspath = os.path.join(outdir, product)
                with safe_open(abspath, mode='w') as fp:
                    fp.write(product)
                return abspath

            data_by_target[target].add_abs_paths(outdir,
                                                 map(create_product, products))
            yield temporary_dir

    @contextmanager
    def add_products(self, context_products, product_type, target, *products):
        product_mapping = context_products.get(product_type)
        with temporary_dir() as outdir:

            def create_product(product):
                with safe_open(os.path.join(outdir, product), mode='w') as fp:
                    fp.write(product)
                return product

            product_mapping.add(target, outdir, map(create_product, products))
            yield temporary_dir
Example #10
0
class BaseTest(unittest.TestCase):
    """A baseclass useful for tests requiring a temporary buildroot."""
    def build_path(self, relpath):
        """Returns the canonical BUILD file path for the given relative build path."""
        if os.path.basename(relpath).startswith('BUILD'):
            return relpath
        else:
            return os.path.join(relpath, 'BUILD')

    def create_dir(self, relpath):
        """Creates a directory under the buildroot.

    relpath: The relative path to the directory from the build root.
    """
        path = os.path.join(self.build_root, relpath)
        safe_mkdir(path)
        return path

    def create_file(self, relpath, contents='', mode='wb'):
        """Writes to a file under the buildroot.

    relpath:  The relative path to the file from the build root.
    contents: A string containing the contents of the file - '' by default..
    mode:     The mode to write to the file in - over-write by default.
    """
        path = os.path.join(self.build_root, relpath)
        with safe_open(path, mode=mode) as fp:
            fp.write(contents)
        return path

    def add_to_build_file(self, relpath, target):
        """Adds the given target specification to the BUILD file at relpath.

    relpath: The relative path to the BUILD file from the build root.
    target:  A string containing the target definition as it would appear in a BUILD file.
    """
        self.create_file(self.build_path(relpath), target, mode='a')
        cls = self.address_mapper._build_file_type
        return cls(root_dir=self.build_root, relpath=self.build_path(relpath))

    def make_target(self,
                    spec='',
                    target_type=Target,
                    dependencies=None,
                    resources=None,
                    derived_from=None,
                    **kwargs):
        address = SyntheticAddress.parse(spec)
        target = target_type(name=address.target_name,
                             address=address,
                             build_graph=self.build_graph,
                             **kwargs)
        dependencies = dependencies or []
        dependencies.extend(resources or [])

        self.build_graph.inject_target(
            target,
            dependencies=[dep.address for dep in dependencies],
            derived_from=derived_from)
        return target

    @property
    def alias_groups(self):
        return BuildFileAliases.create(targets={'target': Dependencies})

    def setUp(self):
        super(BaseTest, self).setUp()
        Goal.clear()
        Subsystem.reset()

        self.real_build_root = BuildRoot().path

        self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT'))
        self.addCleanup(safe_rmtree, self.build_root)

        self.pants_workdir = os.path.join(self.build_root, '.pants.d')
        safe_mkdir(self.pants_workdir)

        self.options = defaultdict(dict)  # scope -> key-value mapping.
        self.options[''] = {
            'pants_workdir': self.pants_workdir,
            'pants_supportdir': os.path.join(self.build_root, 'build-support'),
            'pants_distdir': os.path.join(self.build_root, 'dist'),
            'pants_configdir': os.path.join(self.build_root, 'config'),
            'cache_key_gen_version': '0-test',
        }

        BuildRoot().path = self.build_root
        self.addCleanup(BuildRoot().reset)

        # We need a pants.ini, even if empty. get_buildroot() uses its presence.
        self.create_file('pants.ini')
        self._build_configuration = BuildConfiguration()
        self._build_configuration.register_aliases(self.alias_groups)
        self.build_file_parser = BuildFileParser(self._build_configuration,
                                                 self.build_root)
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser,
                                                     FilesystemBuildFile)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)

    def reset_build_graph(self):
        """Start over with a fresh build graph with no targets in it."""
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser,
                                                     FilesystemBuildFile)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)

    def set_options_for_scope(self, scope, **kwargs):
        self.options[scope].update(kwargs)

    def context(self,
                for_task_types=None,
                options=None,
                target_roots=None,
                console_outstream=None,
                workspace=None):
        for_task_types = for_task_types or []
        options = options or {}

        option_values = defaultdict(dict)
        registered_subsystems = set()
        bootstrap_option_values = None  # We fill these in after registering bootstrap options.

        # We provide our own test-only registration implementation, bypassing argparse.
        # When testing we set option values directly, so we don't care about cmd-line flags, config,
        # env vars etc. In fact, for test isolation we explicitly don't want to look at those.
        # All this does is make the names available in code, with the default values.
        # Individual tests can then override the option values they care about.
        def register_func(on_scope):
            def register(*rargs, **rkwargs):
                scoped_options = option_values[on_scope]
                default = rkwargs.get('default')
                if default is None and rkwargs.get('action') == 'append':
                    default = []
                for flag_name in rargs:
                    option_name = flag_name.lstrip('-').replace('-', '_')
                    scoped_options[option_name] = default

            register.bootstrap = bootstrap_option_values
            register.scope = on_scope
            return register

        # TODO: This sequence is a bit repetitive of the real registration sequence.

        # Register bootstrap options and grab their default values for use in subsequent registration.
        GlobalOptionsRegistrar.register_bootstrap_options(
            register_func(Options.GLOBAL_SCOPE))
        bootstrap_option_values = create_option_values(
            copy.copy(option_values[Options.GLOBAL_SCOPE]))

        # Now register the full global scope options.
        GlobalOptionsRegistrar.register_options(
            register_func(Options.GLOBAL_SCOPE))

        # Now register task and subsystem options for relevant tasks.
        for task_type in for_task_types:
            scope = task_type.options_scope
            if scope is None:
                raise TaskError(
                    'You must set a scope on your task type before using it in tests.'
                )
            task_type.register_options(register_func(scope))
            for subsystem in (set(task_type.global_subsystems())
                              | set(task_type.task_subsystems())
                              | self._build_configuration.subsystems()):
                if subsystem not in registered_subsystems:
                    subsystem.register_options(
                        register_func(subsystem.options_scope))
                    registered_subsystems.add(subsystem)

        # Now default option values override with any caller-specified values.
        # TODO(benjy): Get rid of the options arg, and require tests to call set_options.

        for scope, opts in options.items():
            for key, val in opts.items():
                option_values[scope][key] = val

        for scope, opts in self.options.items():
            for key, val in opts.items():
                option_values[scope][key] = val

        # Make inner scopes inherit option values from their enclosing scopes.
        all_scopes = set(option_values.keys())
        for task_type in for_task_types:  # Make sure we know about pre-task subsystem scopes.
            all_scopes.update(
                [si.scope for si in task_type.known_scope_infos()])
        # Iterating in sorted order guarantees that we see outer scopes before inner scopes,
        # and therefore only have to inherit from our immediately enclosing scope.
        for scope in sorted(all_scopes):
            if scope != Options.GLOBAL_SCOPE:
                enclosing_scope = scope.rpartition('.')[0]
                opts = option_values[scope]
                for key, val in option_values.get(enclosing_scope, {}).items():
                    if key not in opts:  # Inner scope values override the inherited ones.
                        opts[key] = val

        context = create_context(options=option_values,
                                 target_roots=target_roots,
                                 build_graph=self.build_graph,
                                 build_file_parser=self.build_file_parser,
                                 address_mapper=self.address_mapper,
                                 console_outstream=console_outstream,
                                 workspace=workspace)
        Subsystem._options = context.options
        return context

    def tearDown(self):
        SourceRoot.reset()
        FilesystemBuildFile.clear_cache()

    def target(self, spec):
        """Resolves the given target address to a Target object.

    address: The BUILD target address to resolve.

    Returns the corresponding Target or else None if the address does not point to a defined Target.
    """
        address = SyntheticAddress.parse(spec)
        self.build_graph.inject_address_closure(address)
        return self.build_graph.get_target(address)

    def targets(self, spec):
        """Resolves a target spec to one or more Target objects.

    spec: Either BUILD target address or else a target glob using the siblings ':' or
          descendants '::' suffixes.

    Returns the set of all Targets found.
    """

        spec_parser = CmdLineSpecParser(self.build_root, self.address_mapper)
        addresses = list(spec_parser.parse_addresses(spec))
        for address in addresses:
            self.build_graph.inject_address_closure(address)
        targets = [
            self.build_graph.get_target(address) for address in addresses
        ]
        return targets

    def create_files(self, path, files):
        """Writes to a file under the buildroot with contents same as file name.

     path:  The relative path to the file from the build root.
     files: List of file names.
    """
        for f in files:
            self.create_file(os.path.join(path, f), contents=f)

    def create_library(self, path, target_type, name, sources=None, **kwargs):
        """Creates a library target of given type at the BUILD file at path with sources

     path: The relative path to the BUILD file from the build root.
     target_type: valid pants target type.
     name: Name of the library target.
     sources: List of source file at the path relative to path.
     **kwargs: Optional attributes that can be set for any library target.
       Currently it includes support for resources, java_sources, provides
       and dependencies.
    """
        if sources:
            self.create_files(path, sources)
        self.add_to_build_file(
            path,
            dedent('''
          %(target_type)s(name='%(name)s',
            %(sources)s
            %(resources)s
            %(java_sources)s
            %(provides)s
            %(dependencies)s
          )
        ''' % dict(
                target_type=target_type,
                name=name,
                sources=('sources=%s,' % repr(sources) if sources else ''),
                resources=('resources=["%s"],' % kwargs.get('resources')
                           if 'resources' in kwargs else ''),
                java_sources=('java_sources=[%s],' % ','.join(
                    map(lambda str_target: '"%s"' % str_target,
                        kwargs.get('java_sources')))
                              if 'java_sources' in kwargs else ''),
                provides=('provides=%s,' % kwargs.get('provides')
                          if 'provides' in kwargs else ''),
                dependencies=('dependencies=%s,' % kwargs.get('dependencies')
                              if 'dependencies' in kwargs else ''),
            )))
        return self.target('%s:%s' % (path, name))

    def create_resources(self, path, name, *sources):
        return self.create_library(path, 'resources', name, sources)

    @contextmanager
    def workspace(self, *buildfiles):
        with temporary_dir() as root_dir:
            with BuildRoot().temporary(root_dir):
                with pushd(root_dir):
                    for buildfile in buildfiles:
                        touch(os.path.join(root_dir, buildfile))
                    yield os.path.realpath(root_dir)

    def populate_compile_classpath(self, context, classpath=None):
        """
    Helps actual test cases to populate the 'compile_classpath' products data mapping
    in the context, which holds the classpath value for targets.

    :param context: The execution context where the products data mapping lives.
    :param classpath: a list of classpath strings. If not specified, [os.path.join(self.buildroot, 'none')] will be used.
    """
        classpath = classpath or [os.path.join(self.build_root, 'none')]
        compile_classpaths = context.products.get_data('compile_classpath',
                                                       lambda: UnionProducts())
        compile_classpaths.add_for_targets(context.targets(),
                                           [('default', entry)
                                            for entry in classpath])

    @contextmanager
    def add_data(self, context_products, data_type, target, *products):
        make_products = lambda: defaultdict(MultipleRootedProducts)
        data_by_target = context_products.get_data(data_type, make_products)
        with temporary_dir() as outdir:

            def create_product(product):
                abspath = os.path.join(outdir, product)
                with safe_open(abspath, mode='w') as fp:
                    fp.write(product)
                return abspath

            data_by_target[target].add_abs_paths(outdir,
                                                 map(create_product, products))
            yield temporary_dir

    @contextmanager
    def add_products(self, context_products, product_type, target, *products):
        product_mapping = context_products.get(product_type)
        with temporary_dir() as outdir:

            def create_product(product):
                with safe_open(os.path.join(outdir, product), mode='w') as fp:
                    fp.write(product)
                return product

            product_mapping.add(target, outdir, map(create_product, products))
            yield temporary_dir
Example #11
0
class BaseTest(unittest.TestCase):
    """A baseclass useful for tests requiring a temporary buildroot."""

    def build_path(self, relpath):
        """Returns the canonical BUILD file path for the given relative build path."""
        if os.path.basename(relpath).startswith("BUILD"):
            return relpath
        else:
            return os.path.join(relpath, "BUILD")

    def create_dir(self, relpath):
        """Creates a directory under the buildroot.

    relpath: The relative path to the directory from the build root.
    """
        path = os.path.join(self.build_root, relpath)
        safe_mkdir(path)
        return path

    def create_file(self, relpath, contents="", mode="wb"):
        """Writes to a file under the buildroot.

    relpath:  The relative path to the file from the build root.
    contents: A string containing the contents of the file - '' by default..
    mode:     The mode to write to the file in - over-write by default.
    """
        path = os.path.join(self.build_root, relpath)
        with safe_open(path, mode=mode) as fp:
            fp.write(contents)
        return path

    def add_to_build_file(self, relpath, target):
        """Adds the given target specification to the BUILD file at relpath.

    relpath: The relative path to the BUILD file from the build root.
    target:  A string containing the target definition as it would appear in a BUILD file.
    """
        self.create_file(self.build_path(relpath), target, mode="a")
        cls = self.address_mapper._build_file_type
        return cls(root_dir=self.build_root, relpath=self.build_path(relpath))

    def make_target(self, spec="", target_type=Target, dependencies=None, derived_from=None, **kwargs):
        """Creates a target and injects it into the test's build graph.

    :param string spec: The target address spec that locates this target.
    :param type target_type: The concrete target subclass to create this new target from.
    :param list dependencies: A list of target instances this new target depends on.
    :param derived_from: The target this new target was derived from.
    :type derived_from: :class:`pants.base.target.Target`
    """
        address = Address.parse(spec)
        target = target_type(name=address.target_name, address=address, build_graph=self.build_graph, **kwargs)
        dependencies = dependencies or []

        self.build_graph.inject_target(
            target, dependencies=[dep.address for dep in dependencies], derived_from=derived_from
        )

        # TODO(John Sirois): This re-creates a little bit too much work done by the BuildGraph.
        # Fixup the BuildGraph to deal with non BuildFileAddresses better and just leverage it.
        for traversable_dependency_spec in target.traversable_dependency_specs:
            traversable_dependency_address = Address.parse(traversable_dependency_spec, relative_to=address.spec_path)
            traversable_dependency_target = self.build_graph.get_target(traversable_dependency_address)
            if not traversable_dependency_target:
                raise ValueError(
                    "Tests must make targets for traversable dependency specs ahead of them "
                    "being traversed, {} tried to traverse {} which does not exist.".format(
                        target, traversable_dependency_address
                    )
                )
            if traversable_dependency_target not in target.dependencies:
                self.build_graph.inject_dependency(dependent=target.address, dependency=traversable_dependency_address)
                target.mark_transitive_invalidation_hash_dirty()

        return target

    @property
    def alias_groups(self):
        # NB: In a normal pants deployment, 'target' is an alias for
        # `pants.backend.core.targets.dependencies.Dependencies`.  We avoid that dependency on the core
        # backend here since the `BaseTest` is used by lower level tests in base and since the
        # `Dependencies` type itself is nothing more than an alias for Target that carries along a
        # pydoc for the BUILD dictionary.
        return BuildFileAliases(targets={"target": Target})

    def setUp(self):
        super(BaseTest, self).setUp()
        Goal.clear()
        Subsystem.reset()

        self.real_build_root = BuildRoot().path

        self.build_root = os.path.realpath(mkdtemp(suffix="_BUILD_ROOT"))
        self.addCleanup(safe_rmtree, self.build_root)

        self.pants_workdir = os.path.join(self.build_root, ".pants.d")
        safe_mkdir(self.pants_workdir)

        self.options = defaultdict(dict)  # scope -> key-value mapping.
        self.options[""] = {
            "pants_workdir": self.pants_workdir,
            "pants_supportdir": os.path.join(self.build_root, "build-support"),
            "pants_distdir": os.path.join(self.build_root, "dist"),
            "pants_configdir": os.path.join(self.build_root, "config"),
            "cache_key_gen_version": "0-test",
        }

        BuildRoot().path = self.build_root
        self.addCleanup(BuildRoot().reset)

        # We need a pants.ini, even if empty. get_buildroot() uses its presence.
        self.create_file("pants.ini")
        self._build_configuration = BuildConfiguration()
        self._build_configuration.register_aliases(self.alias_groups)
        self.build_file_parser = BuildFileParser(self._build_configuration, self.build_root)
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)

    def buildroot_files(self, relpath=None):
        """Returns the set of all files under the test build root.

    :param string relpath: If supplied, only collect files from this subtree.
    :returns: All file paths found.
    :rtype: set
    """

        def scan():
            for root, dirs, files in os.walk(os.path.join(self.build_root, relpath or "")):
                for f in files:
                    yield os.path.relpath(os.path.join(root, f), self.build_root)

        return set(scan())

    def reset_build_graph(self):
        """Start over with a fresh build graph with no targets in it."""
        self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile)
        self.build_graph = BuildGraph(address_mapper=self.address_mapper)

    def set_options_for_scope(self, scope, **kwargs):
        self.options[scope].update(kwargs)

    def context(
        self,
        for_task_types=None,
        options=None,
        target_roots=None,
        console_outstream=None,
        workspace=None,
        for_subsystems=None,
    ):

        optionables = set()
        extra_scopes = set()

        for_subsystems = for_subsystems or ()
        for subsystem in for_subsystems:
            if subsystem.options_scope is None:
                raise TaskError("You must set a scope on your subsystem type before using it in tests.")
            optionables.add(subsystem)

        for_task_types = for_task_types or ()
        for task_type in for_task_types:
            scope = task_type.options_scope
            if scope is None:
                raise TaskError("You must set a scope on your task type before using it in tests.")
            optionables.add(task_type)
            extra_scopes.update([si.scope for si in task_type.known_scope_infos()])
            optionables.update(
                Subsystem.closure(
                    set([dep.subsystem_cls for dep in task_type.subsystem_dependencies_iter()])
                    | self._build_configuration.subsystems()
                )
            )

        # Now default the option values and override with any caller-specified values.
        # TODO(benjy): Get rid of the options arg, and require tests to call set_options.
        options = options.copy() if options else {}
        for s, opts in self.options.items():
            scoped_opts = options.setdefault(s, {})
            scoped_opts.update(opts)

        option_values = create_options_for_optionables(optionables, extra_scopes=extra_scopes, options=options)

        context = create_context(
            options=option_values,
            target_roots=target_roots,
            build_graph=self.build_graph,
            build_file_parser=self.build_file_parser,
            address_mapper=self.address_mapper,
            console_outstream=console_outstream,
            workspace=workspace,
        )
        Subsystem._options = context.options
        return context

    def tearDown(self):
        SourceRoot.reset()
        FilesystemBuildFile.clear_cache()
        Subsystem.reset()

    def target(self, spec):
        """Resolves the given target address to a Target object.

    address: The BUILD target address to resolve.

    Returns the corresponding Target or else None if the address does not point to a defined Target.
    """
        address = Address.parse(spec)
        self.build_graph.inject_address_closure(address)
        return self.build_graph.get_target(address)

    def targets(self, spec):
        """Resolves a target spec to one or more Target objects.

    spec: Either BUILD target address or else a target glob using the siblings ':' or
          descendants '::' suffixes.

    Returns the set of all Targets found.
    """

        spec_parser = CmdLineSpecParser(self.build_root, self.address_mapper)
        addresses = list(spec_parser.parse_addresses(spec))
        for address in addresses:
            self.build_graph.inject_address_closure(address)
        targets = [self.build_graph.get_target(address) for address in addresses]
        return targets

    def create_files(self, path, files):
        """Writes to a file under the buildroot with contents same as file name.

     path:  The relative path to the file from the build root.
     files: List of file names.
    """
        for f in files:
            self.create_file(os.path.join(path, f), contents=f)

    def create_library(self, path, target_type, name, sources=None, **kwargs):
        """Creates a library target of given type at the BUILD file at path with sources

     path: The relative path to the BUILD file from the build root.
     target_type: valid pants target type.
     name: Name of the library target.
     sources: List of source file at the path relative to path.
     **kwargs: Optional attributes that can be set for any library target.
       Currently it includes support for resources, java_sources, provides
       and dependencies.
    """
        if sources:
            self.create_files(path, sources)
        self.add_to_build_file(
            path,
            dedent(
                """
          %(target_type)s(name='%(name)s',
            %(sources)s
            %(resources)s
            %(java_sources)s
            %(provides)s
            %(dependencies)s
          )
        """
                % dict(
                    target_type=target_type,
                    name=name,
                    sources=("sources=%s," % repr(sources) if sources else ""),
                    resources=('resources=["%s"],' % kwargs.get("resources") if "resources" in kwargs else ""),
                    java_sources=(
                        "java_sources=[%s],"
                        % ",".join(map(lambda str_target: '"%s"' % str_target, kwargs.get("java_sources")))
                        if "java_sources" in kwargs
                        else ""
                    ),
                    provides=("provides=%s," % kwargs.get("provides") if "provides" in kwargs else ""),
                    dependencies=("dependencies=%s," % kwargs.get("dependencies") if "dependencies" in kwargs else ""),
                )
            ),
        )
        return self.target("%s:%s" % (path, name))

    def create_resources(self, path, name, *sources):
        return self.create_library(path, "resources", name, sources)

    @contextmanager
    def workspace(self, *buildfiles):
        with temporary_dir() as root_dir:
            with BuildRoot().temporary(root_dir):
                with pushd(root_dir):
                    for buildfile in buildfiles:
                        touch(os.path.join(root_dir, buildfile))
                    yield os.path.realpath(root_dir)

    def populate_compile_classpath(self, context, classpath=None):
        """
    Helps actual test cases to populate the 'compile_classpath' products data mapping
    in the context, which holds the classpath value for targets.

    :param context: The execution context where the products data mapping lives.
    :param classpath: a list of classpath strings. If not specified,
                      [os.path.join(self.buildroot, 'none')] will be used.
    """
        classpath = classpath or [os.path.join(self.build_root, "none")]
        compile_classpaths = context.products.get_data("compile_classpath", lambda: UnionProducts())
        compile_classpaths.add_for_targets(context.targets(), [("default", entry) for entry in classpath])

    @contextmanager
    def add_data(self, context_products, data_type, target, *products):
        make_products = lambda: defaultdict(MultipleRootedProducts)
        data_by_target = context_products.get_data(data_type, make_products)
        with temporary_dir() as outdir:

            def create_product(product):
                abspath = os.path.join(outdir, product)
                with safe_open(abspath, mode="w") as fp:
                    fp.write(product)
                return abspath

            data_by_target[target].add_abs_paths(outdir, map(create_product, products))
            yield temporary_dir

    @contextmanager
    def add_products(self, context_products, product_type, target, *products):
        product_mapping = context_products.get(product_type)
        with temporary_dir() as outdir:

            def create_product(product):
                with safe_open(os.path.join(outdir, product), mode="w") as fp:
                    fp.write(product)
                return product

            product_mapping.add(target, outdir, map(create_product, products))
            yield temporary_dir