<filename>src/tests/fidl/source_compatibility/gen/reverse_test.py
import os
from typing import Dict, Optional, Tuple

from util import parse_step, prepend_step
from types_ import *

FidlDefs = Dict[FidlRef, FidlDef]


def reverse_test(test: CompatTest) -> Tuple[CompatTest, Dict[str, str]]:
    """
    Returns a new CompatTest representing the reverse of the provided test,
    as well as a mapping from each file in the input test to its new name
    in the output test (for example, hlcpp/before.cc might become hlcpp/after.cc
    in the reversed test). Each entry is a path relative to the test root.
    """
    title = test.title
    max_step_num = get_num_steps(test)
    fidl, old_to_new_fidl = reverse_fidl_steps(test.fidl, max_step_num)

    bindings: [str, Steps] = {}
    old_to_new: [str, str] = {}
    for b, steps in test.bindings.items():
        rev_steps, old_to_new_src = reverse_src_steps(
            steps, old_to_new_fidl, max_step_num)
        bindings[b] = rev_steps
        old_to_new.update(old_to_new_src)

    # convert from FidlRefs to full source paths
    old_to_new_fidl = {
        test.fidl[old].source: fidl[new].source
        for old, new in old_to_new_fidl.items()
    }
    old_to_new.update(old_to_new_fidl)

    return CompatTest(title, fidl, bindings), old_to_new


def reverse_fidl_steps(fidl: FidlDefs, max_step_num: int) -> (
        FidlDefs, Dict[FidlRef, FidlRef]):
    # we assume that alphabetical ordering will match chronological ordering
    # (this is the case based on current naming conventions, e.g. step_0N_foo)
    refs = sorted(fidl.keys())
    reversed_refs = reverse_steps(refs, max_step_num)
    instructions = reverse_instructions(
        [list(s.instructions) for s in fidl.values()])

    # we hardcode this below so double check that the input paths have the
    # expected format
    for s in fidl.values():
        assert s.source.startswith('fidl/') and s.source.endswith('.test.fidl')

    result: FidlDefs = {}
    old_to_new: Dict[FidlRef, FidlRef] = {}
    for old_name, new_name, instructions in zip(reversed(refs), reversed_refs,
                                                instructions):
        result[new_name] = FidlDef(f'fidl/{new_name}.test.fidl', instructions)
        old_to_new[old_name] = new_name
    return result, old_to_new


def reverse_src_steps(
        transition: Steps, old_to_new_fidl: Dict[FidlRef, FidlRef],
        max_step_num: int) -> (Steps, Dict[str, str]):
    # accumulate all src step paths
    src_steps: List[str] = [transition.starting_src]
    src_steps.extend(
        [s.source for s in transition.steps if isinstance(s, SourceStep)])

    # get a mapping from old path to new path (each key value pair refers to the
    # same source file)
    reversed_steps = reverse_steps(src_steps, max_step_num)
    old_to_new_src = dict(zip(reversed(src_steps), reversed_steps))

    # accumulate all steps in a list (starting fidl/src order is determined by
    assert transition.steps, 'transition has at least one change'
    all_steps = [transition.starting_fidl, transition.starting_src
                ] if isinstance(transition.steps[0], FidlStep) else [
                    transition.starting_src, transition.starting_fidl
                ]
    all_steps.extend(
        [
            s.source if isinstance(s, SourceStep) else s.fidl
            for s in transition.steps
        ])

    if all_steps[-1] in old_to_new_fidl:
        starting_fidl = old_to_new_fidl[all_steps.pop()]
        starting_src = old_to_new_src[all_steps.pop()]
    else:
        starting_src = old_to_new_src[all_steps.pop()]
        starting_fidl = old_to_new_fidl[all_steps.pop()]
    reversed_steps = []
    instructions = reversed(
        [s.instructions for s in transition.steps if isinstance(s, SourceStep)])
    for step in reversed(all_steps):
        if step in old_to_new_fidl:
            new = old_to_new_fidl[step]
            reversed_steps.append(
                FidlStep(fidl=new, step_num=parse_step(new)[1]))
        else:
            new = old_to_new_src[step]
            reversed_steps.append(
                SourceStep(
                    source=new,
                    step_num=parse_step(new)[1],
                    instructions=next(instructions)))
    return Steps(starting_fidl, starting_src, reversed_steps), old_to_new_src


def reverse_steps(steps: List[str], max_step_num: int) -> List[int]:
    """
    Takes a list of strings composed of step number and names, and returns what
    they should be when reversed, e.g.
    >>> reverse_steps(['step_00_foo', 'step_01_bar', 'step_03_baz'], 3)
    ['step_00_baz', 'step_02_bar', 'step_03_foo']
    This function will preserve any leading parts of a path and any file
    extensions.
    """
    # separate out just the filenames
    heads, steps = zip(*[os.path.split(s) for s in steps])

    # separate out step numbers from names
    names, step_nums = zip(*[parse_step(s) for s in steps])
    new_step_nums = reverse_step_nums(step_nums, max_step_num)
    # merge the step numbers and names back together
    result = [prepend_step(s, n) for s, n in zip(names, new_step_nums)]

    # re-prepend the paths back onto the reversed filenames
    return [os.path.join(h, r) for h, r in zip(reversed(heads), result)]


def reverse_step_nums(nums: List[int], max_step_num: int) -> List[int]:
    assert nums[
        0] == 0, 'every sequence of steps needs to have an initial state'
    return [0] + [max_step_num + 1 - n for n in reversed(nums[1:])]


def reverse_instructions(ins: List[List[str]]) -> List[List[str]]:
    assert ins[0] == [], 'initial state shouldnt have associated instructions'
    return [[]] + list(reversed(ins[1:]))


def get_num_steps(test: CompatTest) -> int:
    all_steps = [s for steps in test.bindings.values() for s in steps.steps]
    return max([s.step_num for s in all_steps])

<filename>src/tests/fidl/source_compatibility/gen/reverse_test.py
import os
from typing import Dict, Optional, Tuple

from util import parse_step, prepend_step
from types_ import *

FidlDefs = Dict[FidlRef, FidlDef]


def reverse_test(test: CompatTest) -> Tuple[CompatTest, Dict[str, str]]:
    """
    Returns a new CompatTest representing the reverse of the provided test,
    as well as a mapping from each file in the input test to its new name
    in the output test (for example, hlcpp/before.cc might become hlcpp/after.cc
    in the reversed test). Each entry is a path relative to the test root.
    """
    title = test.title
    max_step_num = get_num_steps(test)
    fidl, old_to_new_fidl = reverse_fidl_steps(test.fidl, max_step_num)

    bindings: [str, Steps] = {}
    old_to_new: [str, str] = {}
    for b, steps in test.bindings.items():
        rev_steps, old_to_new_src = reverse_src_steps(
            steps, old_to_new_fidl, max_step_num)
        bindings[b] = rev_steps
        old_to_new.update(old_to_new_src)

    # convert from FidlRefs to full source paths
    old_to_new_fidl = {
        test.fidl[old].source: fidl[new].source
        for old, new in old_to_new_fidl.items()
    }
    old_to_new.update(old_to_new_fidl)

    return CompatTest(title, fidl, bindings), old_to_new


def reverse_fidl_steps(fidl: FidlDefs, max_step_num: int) -> (
        FidlDefs, Dict[FidlRef, FidlRef]):
    # we assume that alphabetical ordering will match chronological ordering
    # (this is the case based on current naming conventions, e.g. step_0N_foo)
    refs = sorted(fidl.keys())
    reversed_refs = reverse_steps(refs, max_step_num)
    instructions = reverse_instructions(
        [list(s.instructions) for s in fidl.values()])

    # we hardcode this below so double check that the input paths have the
    # expected format
    for s in fidl.values():
        assert s.source.startswith('fidl/') and s.source.endswith('.test.fidl')

    result: FidlDefs = {}
    old_to_new: Dict[FidlRef, FidlRef] = {}
    for old_name, new_name, instructions in zip(reversed(refs), reversed_refs,
                                                instructions):
        result[new_name] = FidlDef(f'fidl/{new_name}.test.fidl', instructions)
        old_to_new[old_name] = new_name
    return result, old_to_new


def reverse_src_steps(
        transition: Steps, old_to_new_fidl: Dict[FidlRef, FidlRef],
        max_step_num: int) -> (Steps, Dict[str, str]):
    # accumulate all src step paths
    src_steps: List[str] = [transition.starting_src]
    src_steps.extend(
        [s.source for s in transition.steps if isinstance(s, SourceStep)])

    # get a mapping from old path to new path (each key value pair refers to the
    # same source file)
    reversed_steps = reverse_steps(src_steps, max_step_num)
    old_to_new_src = dict(zip(reversed(src_steps), reversed_steps))

    # accumulate all steps in a list (starting fidl/src order is determined by
    assert transition.steps, 'transition has at least one change'
    all_steps = [transition.starting_fidl, transition.starting_src
                ] if isinstance(transition.steps[0], FidlStep) else [
                    transition.starting_src, transition.starting_fidl
                ]
    all_steps.extend(
        [
            s.source if isinstance(s, SourceStep) else s.fidl
            for s in transition.steps
        ])

    if all_steps[-1] in old_to_new_fidl:
        starting_fidl = old_to_new_fidl[all_steps.pop()]
        starting_src = old_to_new_src[all_steps.pop()]
    else:
        starting_src = old_to_new_src[all_steps.pop()]
        starting_fidl = old_to_new_fidl[all_steps.pop()]
    reversed_steps = []
    instructions = reversed(
        [s.instructions for s in transition.steps if isinstance(s, SourceStep)])
    for step in reversed(all_steps):
        if step in old_to_new_fidl:
            new = old_to_new_fidl[step]
            reversed_steps.append(
                FidlStep(fidl=new, step_num=parse_step(new)[1]))
        else:
            new = old_to_new_src[step]
            reversed_steps.append(
                SourceStep(
                    source=new,
                    step_num=parse_step(new)[1],
                    instructions=next(instructions)))
    return Steps(starting_fidl, starting_src, reversed_steps), old_to_new_src


def reverse_steps(steps: List[str], max_step_num: int) -> List[int]:
    """
    Takes a list of strings composed of step number and names, and returns what
    they should be when reversed, e.g.
    >>> reverse_steps(['step_00_foo', 'step_01_bar', 'step_03_baz'], 3)
    ['step_00_baz', 'step_02_bar', 'step_03_foo']
    This function will preserve any leading parts of a path and any file
    extensions.
    """
    # separate out just the filenames
    heads, steps = zip(*[os.path.split(s) for s in steps])

    # separate out step numbers from names
    names, step_nums = zip(*[parse_step(s) for s in steps])
    new_step_nums = reverse_step_nums(step_nums, max_step_num)
    # merge the step numbers and names back together
    result = [prepend_step(s, n) for s, n in zip(names, new_step_nums)]

    # re-prepend the paths back onto the reversed filenames
    return [os.path.join(h, r) for h, r in zip(reversed(heads), result)]


def reverse_step_nums(nums: List[int], max_step_num: int) -> List[int]:
    assert nums[
        0] == 0, 'every sequence of steps needs to have an initial state'
    return [0] + [max_step_num + 1 - n for n in reversed(nums[1:])]


def reverse_instructions(ins: List[List[str]]) -> List[List[str]]:
    assert ins[0] == [], 'initial state shouldnt have associated instructions'
    return [[]] + list(reversed(ins[1:]))


def get_num_steps(test: CompatTest) -> int:
    all_steps = [s for steps in test.bindings.values() for s in steps.steps]
    return max([s.step_num for s in all_steps])

Returns a new CompatTest representing the reverse of the provided test,     as well as a mapping from each file in the input test to its new name     in the output test (for example, hlcpp/before.cc might become hlcpp/after.cc     in the reversed test). Each entry is a path relative to the test root. # convert from FidlRefs to full source paths # we assume that alphabetical ordering will match chronological ordering # (this is the case based on current naming conventions, e.g. step_0N_foo) # we hardcode this below so double check that the input paths have the # expected format # accumulate all src step paths # get a mapping from old path to new path (each key value pair refers to the # same source file) # accumulate all steps in a list (starting fidl/src order is determined by Takes a list of strings composed of step number and names, and returns what     they should be when reversed, e.g.     >>> reverse_steps(['step_00_foo', 'step_01_bar', 'step_03_baz'], 3)     ['step_00_baz', 'step_02_bar', 'step_03_foo']     This function will preserve any leading parts of a path and any file     extensions. # separate out just the filenames # separate out step numbers from names # merge the step numbers and names back together # re-prepend the paths back onto the reversed filenames