def test_sample_grandchild(self, tmpdir, spec_type, target,
                               atacseq_piface_data, atac_pipe_name):
        """ The subtype to be used can be a grandchild of Sample. """

        pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)
        intermediate_sample_subtype = "Middle"
        leaf_sample_subtype = "Leaf"

        intermediate_subtype_lines = _class_definition_lines(
            intermediate_sample_subtype, Sample.__name__)
        leaf_subtype_lines = _class_definition_lines(
            leaf_sample_subtype, intermediate_sample_subtype)
        with open(pipe_path, 'w') as pipe_mod_file:
            pipe_mod_file.write("{}\n\n".format(SAMPLE_IMPORT))
            for l in intermediate_subtype_lines:
                pipe_mod_file.write(l)
            pipe_mod_file.write("\n\n")
            for l in leaf_subtype_lines:
                pipe_mod_file.write(l)

        atacseq_piface_data[atac_pipe_name][SUBTYPES_KEY] = \
                target if spec_type == "single" else \
                {ATAC_PROTOCOL_NAME: target}
        conf_path = _write_config_data(
            protomap={ATAC_PROTOCOL_NAME: atac_pipe_name},
            conf_data=atacseq_piface_data,
            dirpath=tmpdir.strpath)

        piface = ProtocolInterface(conf_path)
        subtype = piface.fetch_sample_subtype(protocol=ATAC_PROTOCOL_NAME,
                                              strict_pipe_key=atac_pipe_name,
                                              full_pipe_path=pipe_path)

        assert target == subtype.__name__
    def test_subtype_is_not_Sample(self, tmpdir, atac_pipe_name, subtype_name,
                                   test_type,
                                   atacseq_piface_data_with_subtypes,
                                   subtypes_section_spec_type):
        """ Subtype in interface but not in pipeline is exceptional. """

        pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)

        # Write out pipeline module file with non-Sample class definition.
        lines = _class_definition_lines(subtype_name, name_super_type="object")
        with open(pipe_path, 'w') as pipe_module_file:
            pipe_module_file.write("{}\n\n".format(SAMPLE_IMPORT))
            for l in lines:
                pipe_module_file.write(l)

        # Create the ProtocolInterface and do the test call.
        path_config_file = _write_config_data(
            protomap={ATAC_PROTOCOL_NAME: atac_pipe_name},
            conf_data=atacseq_piface_data_with_subtypes,
            dirpath=tmpdir.strpath)
        piface = ProtocolInterface(path_config_file)
        with pytest.raises(ValueError):
            piface.fetch_sample_subtype(protocol=ATAC_PROTOCOL_NAME,
                                        strict_pipe_key=atac_pipe_name,
                                        full_pipe_path=pipe_path)
    def test_subtypes_list(self, tmpdir, atac_pipe_name, atacseq_piface_data,
                           spec_type):
        """ As singleton or within mapping, only 1 subtype allowed. """

        pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)

        # Define the classes, writing them in the pipeline module file.
        subtype_names = ["ArbitraryA", "PlaceholderB"]
        with open(pipe_path, 'w') as pipe_module_file:
            pipe_module_file.write("{}\n\n".format(SAMPLE_IMPORT))
            for subtype_name in subtype_names:
                # Have the classes be Sample subtypes.
                for line in _class_definition_lines(
                        subtype_name, name_super_type=Sample.__name__):
                    pipe_module_file.write(line)
                pipe_module_file.write("\n\n")

        # Update the ProtocolInterface data.
        subtype_section = subtype_names if spec_type == "single" \
                else {ATAC_PROTOCOL_NAME: subtype_names}
        atacseq_piface_data[atac_pipe_name][SUBTYPES_KEY] = subtype_section

        # Create the ProtocolInterface.
        conf_path = _write_config_data(
            protomap={ATAC_PROTOCOL_NAME: atac_pipe_name},
            conf_data=atacseq_piface_data,
            dirpath=tmpdir.strpath)
        piface = ProtocolInterface(conf_path)

        # We don't really care about exception type, just that one arises.
        with pytest.raises(Exception):
            piface.fetch_sample_subtype(protocol=ATAC_PROTOCOL_NAME,
                                        strict_pipe_key=atac_pipe_name,
                                        full_pipe_path=pipe_path)
    def test_no_subtypes_section(self, tmpdir, path_config_file,
                                 atac_pipe_name, num_sample_subclasses,
                                 decoy_class):
        """ DEPENDS ON PIPELINE MODULE CONTENT """

        # Basic values to invoke the function under test
        pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)
        piface = ProtocolInterface(path_config_file)

        # How to define the Sample subtypes (and non-subtype)
        sample_subclass_basename = "SampleSubclass"
        sample_lines = [
            "class {basename}{index}(Sample):",
            "\tdef __init__(*args, **kwargs):",
            "\t\tsuper({basename}{index}, self).__init__(*args, **kwargs)"
        ]
        non_sample_class_lines = [
            "class NonSample(object):", "\tdef __init__(self):",
            "\t\tsuper(NonSample, self).__init__()"
        ]

        # We expect the subtype iff there's just one Sample subtype.
        if num_sample_subclasses == 1:
            exp_subtype_name = "{}0".format(sample_subclass_basename)
        else:
            exp_subtype_name = Sample.__name__

        # Fill in the class definition template lines.
        def populate_sample_lines(n_classes):
            return [[
                sample_lines[0].format(basename=sample_subclass_basename,
                                       index=class_index), sample_lines[1],
                sample_lines[2].format(basename=sample_subclass_basename,
                                       index=class_index)
            ] for class_index in range(n_classes)]

        # Determine the groups of lines to permute.
        class_lines_pool = populate_sample_lines(num_sample_subclasses)
        if decoy_class:
            class_lines_pool.append(non_sample_class_lines)

        # Subtype fetch is independent of class declaration order,
        # so validate each permutation.
        for lines_order in itertools.permutations(class_lines_pool):
            # Write out class declarations and invoke the function under test.
            _create_module(lines_by_class=lines_order, filepath=pipe_path)
            subtype = piface.fetch_sample_subtype(
                protocol=ATAC_PROTOCOL_NAME,
                strict_pipe_key=atac_pipe_name,
                full_pipe_path=pipe_path)

            # Make the assertion on subtype name, getting additional
            # information about the module that we defined if there's failure.
            try:
                assert exp_subtype_name == subtype.__name__
            except AssertionError:
                with open(pipe_path, 'r') as f:
                    print("PIPELINE MODULE LINES: {}".format("".join(
                        f.readlines())))
                raise
    def test_problematic_import_builtin_exception(self, tmpdir, error_type,
                                                  atac_pipe_name,
                                                  atacseq_piface_data):
        """ Base Sample is used if builtin exception on pipeline import. """

        # Values needed for object creation and function invocation
        protocol = ATAC_PROTOCOL_NAME
        protocol_mapping = {protocol: atac_pipe_name}
        full_pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)

        # Modify the data for the ProtocolInterface and create it.
        atacseq_piface_data[atac_pipe_name][SUBTYPES_KEY] = \
                {protocol: "IrrelevantClassname"}
        conf_path = _write_config_data(protomap=protocol_mapping,
                                       conf_data=atacseq_piface_data,
                                       dirpath=tmpdir.strpath)
        piface = ProtocolInterface(conf_path)

        # We want to test the effect of an encounter with an exception during
        # the import attempt, so patch the relevant function with a function
        # to raise the parameterized exception type.
        with mock.patch("looper.utils.import_from_source",
                        side_effect=error_type()):
            subtype = piface.fetch_sample_subtype(
                protocol=protocol,
                strict_pipe_key=atac_pipe_name,
                full_pipe_path=full_pipe_path)
        # When the import hits an exception, the base Sample type is used.
        assert subtype is Sample
 def test_absolute_path(self, atacseq_piface_data, path_config_file, tmpdir,
                        pipe_path, expected_path_base, atac_pipe_name):
     """ Absolute path regardless of variables works as pipeline path. """
     exp_path = os.path.join(tmpdir.strpath, expected_path_base,
                             atac_pipe_name)
     piface = ProtocolInterface(path_config_file)
     _, obs_path, _ = piface.finalize_pipeline_key_and_paths(atac_pipe_name)
     assert exp_path == obs_path
 def test_non_dot_relpath_becomes_absolute(self, atacseq_piface_data,
                                           path_config_file, tmpdir,
                                           pipe_path, atac_pipe_name):
     """ Relative pipeline path is made absolute when requested by key. """
     # TODO: constant-ify "path" and "ATACSeq.py", as well as possibly "pipelines"
     # and "protocol_mapping" section names of PipelineInterface
     exp_path = os.path.join(tmpdir.strpath, pipe_path, atac_pipe_name)
     piface = ProtocolInterface(path_config_file)
     _, obs_path, _ = piface.finalize_pipeline_key_and_paths(atac_pipe_name)
     assert exp_path == obs_path
    def test_no_path(self, atacseq_piface_data, path_config_file,
                     atac_pipe_name):
        """ Without explicit path, pipeline is assumed parallel to config. """

        piface = ProtocolInterface(path_config_file)

        # The pipeline is assumed to live alongside its configuration file.
        config_dirpath = os.path.dirname(path_config_file)
        expected_pipe_path = os.path.join(config_dirpath, atac_pipe_name)

        _, full_pipe_path, _ = \
                piface.finalize_pipeline_key_and_paths(atac_pipe_name)
        assert expected_pipe_path == full_pipe_path
 def test_warns_about_nonexistent_pipeline_script_path(
         self, atacseq_piface_data, path_config_file, tmpdir, pipe_path,
         atac_pipe_name):
     """ Nonexistent, resolved pipeline script path generates warning. """
     name_log_file = "temp-test-log.txt"
     path_log_file = os.path.join(tmpdir.strpath, name_log_file)
     temp_hdlr = logging.FileHandler(path_log_file, mode='w')
     fmt = logging.Formatter(DEV_LOGGING_FMT)
     temp_hdlr.setFormatter(fmt)
     temp_hdlr.setLevel(logging.WARN)
     models._LOGGER.handlers.append(temp_hdlr)
     pi = ProtocolInterface(path_config_file)
     pi.finalize_pipeline_key_and_paths(atac_pipe_name)
     with open(path_log_file, 'r') as logfile:
         loglines = logfile.readlines()
     assert 1 == len(loglines)
     logmsg = loglines[0]
     assert "WARN" in logmsg and pipe_path in logmsg
    def test_relpath_with_dot_becomes_absolute(self, tmpdir, atac_pipe_name,
                                               atacseq_piface_data):
        """ Leading dot drops from relative path, and it's made absolute. """
        path_parts = ["relpath", "to", "pipelines", atac_pipe_name]
        sans_dot_path = os.path.join(*path_parts)
        pipe_path = os.path.join(".", sans_dot_path)
        atacseq_piface_data[atac_pipe_name]["path"] = pipe_path

        exp_path = os.path.join(tmpdir.strpath, sans_dot_path)

        path_config_file = _write_config_data(
            protomap={ATAC_PROTOCOL_NAME: atac_pipe_name},
            conf_data=atacseq_piface_data,
            dirpath=tmpdir.strpath)
        piface = ProtocolInterface(path_config_file)
        _, obs_path, _ = piface.finalize_pipeline_key_and_paths(atac_pipe_name)
        # Dot may remain in path, so assert equality of absolute paths.
        assert os.path.abspath(exp_path) == os.path.abspath(obs_path)
    def test_matches_sample_subtype(self, tmpdir, atac_pipe_name, subtype_name,
                                    atacseq_piface_data):
        """ Fetch of subtype is specific even from among multiple subtypes. """

        # Basic values
        pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)
        decoy_class = "Decoy"
        decoy_proto = "DECOY"

        # Update the ProtocolInterface data and write it out.
        atacseq_piface_data[atac_pipe_name][SUBTYPES_KEY] = {
            ATAC_PROTOCOL_NAME: subtype_name,
            decoy_proto: decoy_class
        }
        conf_path = _write_config_data(protomap={
            ATAC_PROTOCOL_NAME: atac_pipe_name,
            decoy_proto: atac_pipe_name
        },
                                       conf_data=atacseq_piface_data,
                                       dirpath=tmpdir.strpath)

        # Create the collection of definition lines for each class.
        legit_lines = _class_definition_lines(subtype_name, Sample.__name__)
        decoy_lines = _class_definition_lines(decoy_class, Sample.__name__)

        for lines_order in itertools.permutations([legit_lines, decoy_lines]):
            with open(pipe_path, 'w') as pipe_mod_file:
                pipe_mod_file.write("{}\n\n".format(SAMPLE_IMPORT))
                for class_lines in lines_order:
                    for line in class_lines:
                        pipe_mod_file.write(line)
                    pipe_mod_file.write("\n\n")

            # We need the new pipeline module file in place before the
            # ProtocolInterface is created.
            piface = ProtocolInterface(conf_path)
            subtype = piface.fetch_sample_subtype(
                protocol=ATAC_PROTOCOL_NAME,
                strict_pipe_key=atac_pipe_name,
                full_pipe_path=pipe_path)
            assert subtype_name == subtype.__name__
    def test_protocol_match_is_fuzzy(self, tmpdir, mapped_protocol,
                                     atac_pipe_name, requested_protocol,
                                     atacseq_piface_data):
        """ Punctuation and case mismatches are tolerated in protocol name. """

        # Needed to create the ProtocolInterface.
        protomap = {mapped_protocol: atac_pipe_name}
        # Needed to invoke the function under test.
        full_pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)

        # PipelineInterface data provided maps name to actual interface data
        # Mapping, so modify the ATAC-Seq mapping within that.
        # In this test, we're interested in the resolution of the protocol
        # name, that with it we can grab the name of a class. Thus, we
        # need only an arbitrary class name about which we can make the
        # relevant assertion(s).
        test_class_name = "TotallyArbitrary"
        atacseq_piface_data[atac_pipe_name][SUBTYPES_KEY] = \
                test_class_name

        # Write out configuration data and create the ProtocolInterface.
        conf_path = _write_config_data(protomap=protomap,
                                       conf_data=atacseq_piface_data,
                                       dirpath=tmpdir.strpath)
        piface = ProtocolInterface(conf_path)

        # Make the call under test, patching the function protected
        # function that's called iff the protocol name match succeeds.
        with mock.patch("looper.models._import_sample_subtype",
                        return_value=None) as mocked_import:
            # Return value is irrelevant; the effect of the protocol name
            # match/resolution is entirely observable via the argument to the
            # protected import function.
            piface.fetch_sample_subtype(protocol=requested_protocol,
                                        strict_pipe_key=atac_pipe_name,
                                        full_pipe_path=full_pipe_path)
        # When the protocol name match/resolution succeeds, the name of the
        # Sample subtype class to which it was mapped is passed as an
        # argument to the protected import function.
        mocked_import.assert_called_with(full_pipe_path, test_class_name)
    def test_pipeline_key_match_is_strict(self, tmpdir, pipe_key, protocol,
                                          atac_pipe_name,
                                          atacseq_iface_with_resources):
        """ Request for Sample subtype for unmapped pipeline is KeyError. """

        # Create the ProtocolInterface.
        strict_pipe_key = atac_pipe_name
        protocol_mapping = {protocol: strict_pipe_key}
        confpath = _write_config_data(
            protomap=protocol_mapping,
            dirpath=tmpdir.strpath,
            conf_data={strict_pipe_key: atacseq_iface_with_resources})
        piface = ProtocolInterface(confpath)

        # The absolute pipeline path is the pipeline name, joined to the
        # ProtocolInterface's pipelines location. This location is the
        # location from which a Sample subtype import is attempted.
        full_pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)

        # TODO: update to pytest.raises(None) if/when 3.1 adoption.
        # Match between pipeline key specified and the strict key used in
        # the mapping --> no error while mismatch --> error.
        if pipe_key == atac_pipe_name:
            piface.fetch_sample_subtype(protocol,
                                        pipe_key,
                                        full_pipe_path=full_pipe_path)
        else:
            with pytest.raises(KeyError):
                piface.fetch_sample_subtype(protocol,
                                            pipe_key,
                                            full_pipe_path=full_pipe_path)
 def test_subtype_not_implemented(self, tmpdir, atac_pipe_name,
                                  subtype_name, decoy_class,
                                  atacseq_piface_data_with_subtypes,
                                  subtypes_section_spec_type):
     """ Subtype that doesn't extend Sample isn't used. """
     # Create the pipeline module.
     pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)
     lines = _class_definition_lines("Decoy", "object") \
             if decoy_class else []
     with open(pipe_path, 'w') as modfile:
         modfile.write("{}\n\n".format(SAMPLE_IMPORT))
         for l in lines:
             modfile.write(l)
     conf_path = _write_config_data(
         protomap={ATAC_PROTOCOL_NAME: atac_pipe_name},
         conf_data=atacseq_piface_data_with_subtypes,
         dirpath=tmpdir.strpath)
     piface = ProtocolInterface(conf_path)
     with pytest.raises(ValueError):
         piface.fetch_sample_subtype(protocol=ATAC_PROTOCOL_NAME,
                                     strict_pipe_key=atac_pipe_name,
                                     full_pipe_path=pipe_path)
    def test_Sample_as_name(self, tmpdir, subtype_name, atac_pipe_name,
                            subtypes_section_spec_type,
                            atacseq_piface_data_with_subtypes):
        """ A pipeline may redeclare Sample as a subtype name. """

        # General values for the test
        subtype_name = Sample.__name__
        pipe_path = os.path.join(tmpdir.strpath, atac_pipe_name)

        # Define the subtype in the pipeline module.
        lines = [
            "from looper.models import Sample\n",
            "class {}({}):\n".format(subtype_name, subtype_name),
            "\tdef __init__(self, *args, **kwargs):\n",
            "\t\tsuper({}, self).__init__(*args, **kwargs)\n".format(
                subtype_name)
        ]
        with open(pipe_path, 'w') as pipe_module_file:
            for l in lines:
                pipe_module_file.write(l)

        conf_path = _write_config_data(
            protomap={ATAC_PROTOCOL_NAME: atac_pipe_name},
            conf_data=atacseq_piface_data_with_subtypes,
            dirpath=tmpdir.strpath)
        piface = ProtocolInterface(conf_path)
        subtype = piface.fetch_sample_subtype(protocol=ATAC_PROTOCOL_NAME,
                                              strict_pipe_key=atac_pipe_name,
                                              full_pipe_path=pipe_path)

        # Establish that subclass relationship is improper.
        assert issubclass(Sample, Sample)
        # Our subtype derives from base Sample...
        assert issubclass(subtype, Sample)
        # ...but not vice-versa.
        assert not issubclass(Sample, subtype)
        # And we retained the name.
        assert subtype.__name__ == Sample.__name__