# Buggy code
def get_reduced_index(prefix, channels, subdirs, specs):
    # # this block of code is a "combine" step intended to filter out redundant specs
    # # causes a problem with py.test tests/core/test_solve.py -k broken_install
    # specs_map = defaultdict(list)
    # for spec in specs:
    #     specs_map[spec.name].append(spec)
    # consolidated_specs = set()
    # for spec_name, specs_group in iteritems(specs_map):
    #     if len(specs_group) == 1:
    #         consolidated_specs.add(specs_group[0])
    #     elif spec_name == '*':
    #         consolidated_specs.update(specs_group)
    #     else:
    #         keep_specs = []
    #         for spec in specs_group:
    #             if len(spec._match_components) > 1 or spec.target or spec.optional:
    #                 keep_specs.append(spec)
    #         consolidated_specs.update(keep_specs)

    with ThreadLimitedThreadPoolExecutor() as executor:
        channel_urls = all_channel_urls(channels, subdirs=subdirs)
        check_whitelist(channel_urls)

        if context.offline:
            grouped_urls = groupby(lambda url: url.startswith("file://"), channel_urls)
            ignored_urls = grouped_urls.get(False, ())
            if ignored_urls:
                log.info(
                    "Ignoring the following channel urls because mode is offline.%s",
                    dashlist(ignored_urls),
                )
            channel_urls = IndexedSet(grouped_urls.get(True, ()))
        subdir_datas = tuple(SubdirData(Channel(url)) for url in channel_urls)

        records = IndexedSet()
        collected_names = set()
        collected_track_features = set()
        pending_names = set()
        pending_track_features = set()

        def query_all(spec):
            futures = tuple(executor.submit(sd.query, spec) for sd in subdir_datas)
            return tuple(concat(future.result() for future in as_completed(futures)))

        def push_spec(spec):
            name = spec.get_raw_value("name")
            if name and name not in collected_names:
                pending_names.add(name)
            track_features = spec.get_raw_value("track_features")
            if track_features:
                for ftr_name in track_features:
                    if ftr_name not in collected_track_features:
                        pending_track_features.add(ftr_name)

        def push_record(record):
            for _spec in record.combined_depends:
                push_spec(_spec)
            if record.track_features:
                for ftr_name in record.track_features:
                    push_spec(MatchSpec(track_features=ftr_name))

        for spec in specs:
            push_spec(spec)

        while pending_names or pending_track_features:
            while pending_names:
                name = pending_names.pop()
                collected_names.add(name)
                spec = MatchSpec(name)
                new_records = query_all(spec)
                for record in new_records:
                    push_record(record)
                records.update(new_records)

            while pending_track_features:
                feature_name = pending_track_features.pop()
                collected_track_features.add(feature_name)
                spec = MatchSpec(track_features=feature_name)
                new_records = query_all(spec)
                for record in new_records:
                    push_record(record)
                records.update(new_records)

        reduced_index = {Dist(rec): rec for rec in records}

        if prefix is not None:
            _supplement_index_with_prefix(reduced_index, prefix)

        if context.offline or (
            "unknown" in context._argparse_args and context._argparse_args.unknown
        ):
            # This is really messed up right now.  Dates all the way back to
            # https://github.com/conda/conda/commit/f761f65a82b739562a0d997a2570e2b8a0bdc783
            # TODO: revisit this later
            _supplement_index_with_cache(reduced_index)

        # add feature records for the solver
        known_features = set()
        for rec in itervalues(reduced_index):
            known_features.update(concatv(rec.track_features, rec.features))
        known_features.update(context.track_features)
        for ftr_str in known_features:
            rec = make_feature_record(ftr_str)
            reduced_index[Dist(rec)] = rec

        return reduced_index


# Traceback (conda.exceptions.CondaValueError)
$ conda --version
conda 4.4.7
$ conda install -vvv numpy
DEBUG conda.gateways.logging:set_verbosity(148): verbosity set to 3
Solving environment: ...working... DEBUG conda.core.solve:solve_final_state(140): solving prefix /scratch/mc3
specs_to_remove: frozenset()
specs_to_add: frozenset({MatchSpec(name=LowerStrMatch('numpy'))})
prune: False
TRACE conda.core.linked_data:_load_single_record(113): loading prefix record /scratch/mc3/conda-meta/appnope-0.1.0-py36hf537a9a_0.json
TRACE conda.core.linked_data:_load_single_record(113): loading prefix record /scratch/mc3/conda-meta/asn1crypto-0.23.0-py36h782d450_0.json
TRACE conda.core.linked_data:_load_single_record(113): loading prefix record /scratch/mc3/conda-meta/bleach-2.1.1-py36h27c13d8_0.json
[...]
TRACE conda.core.linked_data:_load_single_record(113): loading prefix record /scratch/mc3/conda-meta/zeromq-4.2.2-ha360ad0_2.json
TRACE conda.core.linked_data:_load_single_record(113): loading prefix record /scratch/mc3/conda-meta/zlib-1.2.11-hf3cbc9b_2.json
failed
Traceback (most recent call last):
File "/scratch/mc3/lib/python3.6/site-packages/conda/exceptions.py", line 743, in __call__
return func(*args, **kwargs)
File "/scratch/mc3/lib/python3.6/site-packages/conda/cli/main.py", line 78, in _main
exit_code = do_call(args, p)
File "/scratch/mc3/lib/python3.6/site-packages/conda/cli/conda_argparse.py", line 76, in do_call
exit_code = getattr(module, func_name)(args, parser)
File "/scratch/mc3/lib/python3.6/site-packages/conda/cli/main_install.py", line 11, in execute
install(args, parser, 'install')
File "/scratch/mc3/lib/python3.6/site-packages/conda/cli/install.py", line 236, in install
force_reinstall=context.force,
File "/scratch/mc3/lib/python3.6/site-packages/conda/core/solve.py", line 504, in solve_for_transaction
force_remove, force_reinstall)
File "/scratch/mc3/lib/python3.6/site-packages/conda/core/solve.py", line 437, in solve_for_diff
final_precs = self.solve_final_state(deps_modifier, prune, ignore_pinned, force_remove)
File "/scratch/mc3/lib/python3.6/site-packages/conda/core/solve.py", line 146, in solve_final_state
specs_from_history_map = History(self.prefix).get_requested_specs_map()
File "/scratch/mc3/lib/python3.6/site-packages/conda/history.py", line 187, in get_requested_specs_map
spec_map.update(((s.name, s) for s in update_specs))
File "/scratch/mc3/lib/python3.6/site-packages/conda/history.py", line 187, in <genexpr>
spec_map.update(((s.name, s) for s in update_specs))
File "/scratch/mc3/lib/python3.6/site-packages/conda/history.py", line 186, in <genexpr>
update_specs = (MatchSpec(spec) for spec in request.get('update_specs', ()))
File "/scratch/mc3/lib/python3.6/site-packages/conda/models/match_spec.py", line 39, in __call__
parsed = _parse_spec_str(spec_arg)
File "/scratch/mc3/lib/python3.6/site-packages/conda/models/match_spec.py", line 556, in _parse_spec_str
raise CondaValueError("Invalid MatchSpec: %s" % spec_str)
conda.exceptions.CondaValueError: Invalid MatchSpec: <2.0

# Fixed code
def get_reduced_index(prefix, channels, subdirs, specs):
    # # this block of code is a "combine" step intended to filter out redundant specs
    # # causes a problem with py.test tests/core/test_solve.py -k broken_install
    # specs_map = defaultdict(list)
    # for spec in specs:
    #     specs_map[spec.name].append(spec)
    # consolidated_specs = set()
    # for spec_name, specs_group in iteritems(specs_map):
    #     if len(specs_group) == 1:
    #         consolidated_specs.add(specs_group[0])
    #     elif spec_name == '*':
    #         consolidated_specs.update(specs_group)
    #     else:
    #         keep_specs = []
    #         for spec in specs_group:
    #             if len(spec._match_components) > 1 or spec.target or spec.optional:
    #                 keep_specs.append(spec)
    #         consolidated_specs.update(keep_specs)

    with ThreadLimitedThreadPoolExecutor() as executor:
        channel_urls = all_channel_urls(channels, subdirs=subdirs)
        check_whitelist(channel_urls)

        if context.offline:
            grouped_urls = groupby(lambda url: url.startswith("file://"), channel_urls)
            ignored_urls = grouped_urls.get(False, ())
            if ignored_urls:
                log.info(
                    "Ignoring the following channel urls because mode is offline.%s",
                    dashlist(ignored_urls),
                )
            channel_urls = IndexedSet(grouped_urls.get(True, ()))
        subdir_datas = tuple(SubdirData(Channel(url)) for url in channel_urls)

        records = IndexedSet()
        collected_names = set()
        collected_track_features = set()
        pending_names = set()
        pending_track_features = set()

        def query_all(spec):
            futures = tuple(executor.submit(sd.query, spec) for sd in subdir_datas)
            return tuple(concat(future.result() for future in as_completed(futures)))

        def push_spec(spec):
            name = spec.get_raw_value("name")
            if name and name not in collected_names:
                pending_names.add(name)
            track_features = spec.get_raw_value("track_features")
            if track_features:
                for ftr_name in track_features:
                    if ftr_name not in collected_track_features:
                        pending_track_features.add(ftr_name)

        def push_record(record):
            push_spec(MatchSpec(record.name))
            for _spec in record.combined_depends:
                push_spec(_spec)
            if record.track_features:
                for ftr_name in record.track_features:
                    push_spec(MatchSpec(track_features=ftr_name))

        if prefix:
            for prefix_rec in PrefixData(prefix).iter_records():
                push_record(prefix_rec)
        for spec in specs:
            push_spec(spec)

        while pending_names or pending_track_features:
            while pending_names:
                name = pending_names.pop()
                collected_names.add(name)
                spec = MatchSpec(name)
                new_records = query_all(spec)
                for record in new_records:
                    push_record(record)
                records.update(new_records)

            while pending_track_features:
                feature_name = pending_track_features.pop()
                collected_track_features.add(feature_name)
                spec = MatchSpec(track_features=feature_name)
                new_records = query_all(spec)
                for record in new_records:
                    push_record(record)
                records.update(new_records)

        reduced_index = {Dist(rec): rec for rec in records}

        if prefix is not None:
            _supplement_index_with_prefix(reduced_index, prefix)

        if context.offline or (
            "unknown" in context._argparse_args and context._argparse_args.unknown
        ):
            # This is really messed up right now.  Dates all the way back to
            # https://github.com/conda/conda/commit/f761f65a82b739562a0d997a2570e2b8a0bdc783
            # TODO: revisit this later
            _supplement_index_with_cache(reduced_index)

        # add feature records for the solver
        known_features = set()
        for rec in itervalues(reduced_index):
            known_features.update(concatv(rec.track_features, rec.features))
        known_features.update(context.track_features)
        for ftr_str in known_features:
            rec = make_feature_record(ftr_str)
            reduced_index[Dist(rec)] = rec

        return reduced_index