Esempio n. 1
0
def validate_translation(p4_file, target_dir, p4c_bin):
    log.info("\n\n" + "-" * 70)
    log.info("Analysing %s", p4_file)
    util.check_dir(target_dir)
    fail_dir = target_dir.joinpath("failed")
    # run the p4 compiler and dump all the passes for this file
    passes = gen_p4_passes(p4c_bin, target_dir, p4_file)
    passes = prune_passes(passes)
    p4_py_files = []
    # for each emitted pass, generate a python representation
    for p4_pass in passes:
        p4_path = Path(p4_pass).stem
        py_file = f"{target_dir}/{p4_path}.py"
        result = run_p4_to_py(p4_pass, py_file)
        if result.returncode != util.EXIT_SUCCESS:
            log.error("Failed to translate P4 to Python.")
            log.error("Compiler crashed!")
            util.check_dir(fail_dir)
            with open(f"{fail_dir}/error.txt", 'w+') as err_file:
                err_file.write(result.stderr.decode("utf-8"))
            util.copy_file([p4_pass, py_file], fail_dir)
            return result.returncode
        p4_py_files.append(f"{target_dir}/{p4_path}")
    if len(p4_py_files) < 2:
        log.warning("P4 file did not generate enough passes!")
        return util.EXIT_SKIPPED
    # perform the actual comparison
    result = z3check.z3_check(p4_py_files, fail_dir)
    return result
Esempio n. 2
0
def main(args):

    config = {}
    config["do_validate"] = args.do_validate
    config["use_tofino"] = args.use_tofino
    config["use_blackbox"] = args.use_blackbox
    config["randomize_input"] = args.randomize_input

    # configure logging
    logging.basicConfig(filename=args.log_file,
                        format="%(levelname)s:%(message)s",
                        level=logging.INFO,
                        filemode='w')
    stderr_log = logging.StreamHandler()
    stderr_log.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
    log.addHandler(stderr_log)

    util.check_dir(OUTPUT_DIR)

    # initialize with some pre-configured state
    launch = TestLauncher(config)

    if config["use_tofino"] and config["do_validate"]:
        config["use_blackbox"] = True
        # tofino only supports single threaded mode for now
        for idx in range(args.iterations):
            launch(idx)
        return
    # this sometimes deadlocks, no idea why....
    with Pool(args.num_processes) as p:
        p.map(launch, range(args.iterations))
    return
Esempio n. 3
0
def dump_file(target_dir, p4_file):
    util.check_dir(target_dir)
    target = target_dir.joinpath(p4_file.name)
    try:
        p4_file.rename(target)
    except FileNotFoundError:
        log.warning("Could not move file %s, file not found!", p4_file)
def validate_translation(p4_file,
                         target_dir,
                         p4c_bin,
                         allow_undef=False,
                         dump_info=False):
    info = INFO

    # customize the main info with the new information
    info["compiler"] = str(p4c_bin)
    info["exit_code"] = util.EXIT_SUCCESS
    info["p4z3_bin"] = str(P4Z3_BIN)
    info["out_dir"] = str(target_dir)
    info["input_file"] = str(p4_file)
    info["allow_undef"] = allow_undef
    info["validation_bin"] = f"python3 {__file__}"

    log.info("\n" + "-" * 70)
    log.info("Analysing %s", p4_file)
    start_time = datetime.now()
    util.check_dir(target_dir)
    fail_dir = target_dir.joinpath("failed")
    # run the p4 compiler and dump all the passes for this file
    passes = gen_p4_passes(p4c_bin, target_dir, p4_file)
    passes = prune_passes(passes)
    p4_py_files = []
    # for each emitted pass, generate a python representation
    for p4_pass in passes:
        p4_path = Path(p4_pass).stem
        py_file = f"{target_dir}/{p4_path}.py"
        result = run_p4_to_py(p4_pass, py_file)
        if result.returncode != util.EXIT_SUCCESS:
            log.error("Failed to translate P4 to Python.")
            log.error("Compiler crashed!")
            util.check_dir(fail_dir)
            with open(f"{fail_dir}/error.txt", 'w+') as err_file:
                err_file.write(result.stderr.decode("utf-8"))
            util.copy_file([p4_pass, py_file], fail_dir)
            return result.returncode
        p4_py_files.append(f"{target_dir}/{p4_path}")
    if len(p4_py_files) < 2:
        log.warning("P4 file did not generate enough passes!")
        return util.EXIT_SKIPPED
    # perform the actual comparison
    result, check_info = z3check.z3_check(p4_py_files, fail_dir, allow_undef)
    # merge the two info dicts
    info["exit_code"] = result
    info = {**info, **check_info}
    done_time = datetime.now()
    elapsed = done_time - start_time
    time_str = time.strftime("%H hours %M minutes %S seconds",
                             time.gmtime(elapsed.total_seconds()))
    ms = elapsed.microseconds / 1000
    log.info("Translation validation took %s %s milliseconds.", time_str, ms)
    if dump_info:
        json_name = target_dir.joinpath(f"{p4_file.stem}_info.json")
        log.info("Dumping configuration to %s.", json_name)
        with open(json_name, 'w') as json_file:
            json.dump(info, json_file, indent=2, sort_keys=True)
    return result
def check(idx, config):
    test_id = generate_id()
    test_name = f"{test_id}_{idx}"
    dump_dir = OUTPUT_DIR.joinpath(f"dmp_{test_name}")
    util.check_dir(dump_dir)
    log_file = dump_dir.joinpath(f"{test_name}.log")
    p4_file = dump_dir.joinpath(f"{test_name}.p4")
    seed = int.from_bytes(os.getrandom(8), "big")
    log.info("Testing P4 program: %s - Seed: %s", p4_file.name, seed)
    # generate a random program
    result, p4_file = generate_p4_prog(P4RANDOM_BIN, p4_file, config, seed)
    if result.returncode != util.EXIT_SUCCESS:
        log.error("Failed generate P4 code!")
        dump_result(result, GENERATOR_BUG_DIR, p4_file)
        # reset the dump directory
        util.del_dir(dump_dir)
        return result.returncode
    # check compilation
    result = compile_p4_prog(config["compiler_bin"], p4_file, dump_dir)
    if result.returncode != util.EXIT_SUCCESS:
        if not is_known_bug(result):
            log.error("Failed to compile the P4 code!")
            log.error("Found a new bug!")
            dump_result(result, CRASH_BUG_DIR, p4_file)
            dump_file(CRASH_BUG_DIR, p4_file)
            if config["do_prune"]:
                info_file = CRASH_BUG_DIR.joinpath(f"{p4_file.stem}_info.json")
                info = validation.INFO
                # customize the main info with the new information
                info["compiler"] = str(config["compiler_bin"])
                info["exit_code"] = result.returncode
                info["p4z3_bin"] = str(P4Z3_BIN)
                info["out_dir"] = str(CRASH_BUG_DIR)
                info["input_file"] = str(p4_file)
                info["allow_undef"] = False
                info["err_string"] = result.stderr.decode("utf-8")
                log.error("Dumping configuration to %s.", info_file)
                with open(info_file, 'w') as json_file:
                    json.dump(info, json_file, indent=2, sort_keys=True)
                p4_cmd = f"{PRUNER_BIN} "
                p4_cmd += f"--config {info_file} "
                p4_cmd += f" {CRASH_BUG_DIR.joinpath(f'{p4_file.stem}.p4')} "
                log.error("Pruning P4 file with command %s ", p4_cmd)
                util.start_process(p4_cmd)
        # reset the dump directory
        util.del_dir(dump_dir)
        return result
    # check validation
    if config["do_validate"]:
        result = validate(dump_dir, p4_file, log_file, config)
    elif config["use_blackbox"]:
        result = run_p4_test(dump_dir, p4_file, log_file, config)

    # reset the dump directory
    util.del_dir(dump_dir)
    return result
Esempio n. 6
0
def save_error(err_path, stdout, stderr):
    log.error("*" * 60)
    log.error(stdout.decode("utf-8"))
    log.error("*" * 60)
    log.error(stderr.decode("utf-8"))
    log.error("*" * 60)
    util.check_dir(err_path.parent)
    with open(f"{err_path}", 'w+') as err_file:
        err_file.write(stdout.decode("utf-8"))
        err_file.write(stderr.decode("utf-8"))
Esempio n. 7
0
def main(args):

    if args.randomize_input:
        seed = int.from_bytes(os.getrandom(8), "big")
        z3.set_param(
            "smt.phase_selection",
            5,
            "smt.random_seed",
            seed,
            "smt.arith.random_initial_value",
            True,
            "sat.phase",
            "random",
        )

    config = {}
    config["arch"] = args.arch
    if config["arch"] == "tna":
        config["pipe_name"] = "pipe0_ingress"
        config["ingress_var"] = "ingress"
    elif config["arch"] == "v1model":
        config["pipe_name"] = "ig"
        config["ingress_var"] = "ig"
    elif config["arch"] == "psa":
        config["pipe_name"] = "ingress_ig"
        config["ingress_var"] = "ig"
    else:
        raise RuntimeError("Unsupported test arch \"%s\"!" % config["arch"])

    if args.p4_input:
        p4_input = Path(args.p4_input)
        out_base_dir = Path(args.out_dir)
    else:
        out_base_dir = Path(args.out_dir).joinpath("rnd_test")
        util.check_dir(out_base_dir)
        p4_input = out_base_dir.joinpath("rnd_test.p4")
        # generate a random program from scratch
        generate_p4_prog(P4RANDOM_BIN, p4_input, config)

    if os.path.isfile(p4_input):
        out_dir = out_base_dir.joinpath(p4_input.stem)
        util.del_dir(out_dir)
        config["out_dir"] = out_dir
        config["p4_input"] = p4_input
        result = perform_blackbox_test(config)
    else:
        util.check_dir(out_base_dir)
        for p4_file in list(p4_input.glob("**/*.p4")):
            out_dir = out_base_dir.joinpath(p4_file.stem)
            util.del_dir(out_dir)
            config["out_dir"] = out_dir
            config["p4_input"] = p4_file
            result = perform_blackbox_test(config)
    sys.exit(result)
Esempio n. 8
0
def main(args):
    # configure logging
    logging.basicConfig(filename=args.log_file,
                        format="%(levelname)s:%(message)s",
                        level=logging.INFO,
                        filemode='w')
    stderr_log = logging.StreamHandler()
    stderr_log.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
    logging.getLogger().addHandler(stderr_log)

    if args.randomize_input:
        z3.set_param(
            "smt.phase_selection",
            5,
            "smt.arith.random_initial_value",
            True,
            "sat.phase",
            "random",
        )

    config = {}
    config["use_tofino"] = args.use_tofino
    if args.use_tofino:
        config["pipe_name"] = "Pipeline_ingress"
        config["ingress_var"] = "ingress"
    else:
        config["pipe_name"] = "ig"
        config["ingress_var"] = "ig"

    if args.p4_input:
        p4_input = Path(args.p4_input)
        out_base_dir = Path(args.out_dir)
    else:
        out_base_dir = Path(args.out_dir).joinpath("rnd_test")
        util.check_dir(out_base_dir)
        p4_input = out_base_dir.joinpath("rnd_test.p4")
        # generate a random program from scratch
        generate_random_prog(P4RANDOM_BIN, p4_input, config)

    if os.path.isfile(p4_input):
        out_dir = out_base_dir.joinpath(p4_input.stem)
        util.del_dir(out_dir)
        config["out_dir"] = out_dir
        config["p4_input"] = p4_input
        result = perform_blackbox_test(config)
    else:
        util.check_dir(out_base_dir)
        for p4_file in list(p4_input.glob("**/*.p4")):
            out_dir = out_base_dir.joinpath(p4_file.stem)
            util.del_dir(out_dir)
            config["out_dir"] = out_dir
            config["p4_input"] = p4_file
            result = perform_blackbox_test(config)
    sys.exit(result)
def prune_files(p4_prune_dir, p4_passes):
    util.check_dir(p4_prune_dir)
    for p4_file in p4_passes:
        sed_cmd = "sed -r "
        sed_cmd += "\':a; s%(.*)/\\*.*\\*/%\\1%; ta; /\\/\\*/ !b; N; ba\' "
        sed_cmd += f"{p4_file} "
        sed_cmd += " | sed -r \'/^\\s*$/d\' "
        sed_cmd += f"> {p4_prune_dir}/{p4_file.name}"
        log.debug("Removing comments and whitespace")
        log.debug("Command: %s", sed_cmd)
        util.exec_process(sed_cmd)
    return p4_prune_dir
def gen_p4_passes(p4c_bin, p4_dmp_dir, p4_file):
    util.check_dir(p4_dmp_dir)
    # ignore the compiler output here, for now.
    result = generate_p4_dump(p4c_bin, p4_file, p4_dmp_dir)
    # log.warning(result.stderr.decode('utf-8'))
    # if result.returncode == 1:
    # return []
    p4_passes = list_passes(p4c_bin, p4_file, p4_dmp_dir)
    full_p4_passes = []
    for p4_pass in p4_passes:
        p4_name = f"{p4_file.stem}-{p4_pass}.p4"
        full_p4_pass = p4_dmp_dir.joinpath(p4_name)
        full_p4_passes.append(full_p4_pass)
    return full_p4_passes
Esempio n. 11
0
def main(args):

    p4_input = Path(args.p4_input)
    pass_dir = Path(args.pass_dir)
    p4c_bin = args.p4c_bin
    if os.path.isfile(p4_input):
        pass_dir = pass_dir.joinpath(p4_input.stem)
        util.del_dir(pass_dir)
        result = validate_translation(p4_input, pass_dir, p4c_bin)
        exit(result)
    else:
        util.check_dir(pass_dir)
        for p4_file in list(p4_input.glob("**/*.p4")):
            output_dir = pass_dir.joinpath(p4_file.stem)
            util.del_dir(output_dir)
            result = validate_translation(p4_file, output_dir, p4c_bin)
    exit(util.EXIT_SUCCESS)
Esempio n. 12
0
def get_semantics(config):
    p4_input = config["p4_input"]
    out_dir = config["out_dir"]
    py_file = Path(f"{out_dir}/{p4_input.stem}.py")
    fail_dir = out_dir.joinpath("failed")

    result = run_p4_to_py(p4_input, py_file, config)
    if result.returncode != util.EXIT_SUCCESS:
        log.error("Failed to translate P4 to Python.")
        util.check_dir(fail_dir)
        with open(f"{fail_dir}/error.txt", 'w+') as err_file:
            err_file.write(result.stderr.decode("utf-8"))
        util.copy_file([p4_input, py_file], fail_dir)
        return None, result.returncode
    z3_prog, result = z3check.get_z3_formulization(py_file, fail_dir)
    if result != util.EXIT_SUCCESS:
        return None, result
    return z3_prog, util.EXIT_SUCCESS
def main(args):
    result, config = validate_choice(args)
    if result != util.EXIT_SUCCESS:
        return result

    util.check_dir(OUTPUT_DIR)

    # initialize with some pre-configured state
    launch = TestLauncher(config)

    if config["arch"] == "tna":
        # the tofino tests only support single threaded mode for now
        for idx in range(args.iterations):
            launch(idx)
        return util.EXIT_SUCCESS
    # this sometimes deadlocks, no idea why....
    with Pool(args.num_processes) as p:
        p.map(launch, range(args.iterations))
    return util.EXIT_SUCCESS
Esempio n. 14
0
def get_z3_formulization(p4_file, out_dir=OUT_DIR):

    if p4_file.suffix == ".p4":
        util.check_dir(out_dir)
        py_file = out_dir.joinpath(p4_file.with_suffix(".py").name)
        result = run_p4_to_py(p4_file, py_file)
        p4_file = py_file
        if result.returncode != util.EXIT_SUCCESS:
            log.error("Failed to translate P4 to Python.")
            log.error("Compiler crashed!")
            return None, result.returncode

    p4py_module = get_py_module(p4_file)
    if p4py_module is None:
        return None, util.EXIT_FAILURE
    package, result = get_z3_asts(p4py_module, p4_file)
    if result != util.EXIT_SUCCESS:
        return None, result
    return package, result
Esempio n. 15
0
def perform_blackbox_test(config):
    out_dir = config["out_dir"]
    p4_input = config["p4_input"]
    if out_dir == OUT_DIR:
        out_dir = out_dir.joinpath(p4_input.stem)
    util.check_dir(out_dir)
    util.copy_file(p4_input, out_dir)
    config["out_dir"] = out_dir
    config["p4_input"] = p4_input

    main_formula, pkt_range = get_main_formula(config)
    if main_formula == None or not pkt_range:
        return util.EXIT_FAILURE
    conditions = set()
    # FIXME: Another hack to deal with branch conditions we cannot control
    for child in main_formula.children()[pkt_range]:
        conditions |= get_branch_conditions(child)
    cond_tuple = dissect_conds(config, conditions)
    stf_str = build_test(config, main_formula, cond_tuple, pkt_range)
    # finally, run the test with the stf string we have assembled
    # and return the result of course
    return run_stf_test(config, stf_str)
Esempio n. 16
0
def check(idx, config):
    test_id = generate_id()
    test_name = f"{test_id}_{idx}"
    dump_dir = OUTPUT_DIR.joinpath(f"dmp_{test_name}")
    util.check_dir(dump_dir)
    log_file = dump_dir.joinpath(f"{test_name}.log")
    p4_file = dump_dir.joinpath(f"{test_name}.p4")
    log.info("Testing p4 program %s", p4_file)
    # generate a random program
    result, p4_file = generate_p4_dump(P4RANDOM_BIN, p4_file, config)
    if result.returncode != util.EXIT_SUCCESS:
        log.error("Failed generate P4 code!")
        dump_result(result, GENERATOR_BUG_DIR, p4_file)
        # reset the dump directory
        util.del_dir(dump_dir)
        return result.returncode
    # check compilation
    if config["use_tofino"]:
        result = compile_p4_prog(TOFINO_BIN, p4_file, dump_dir)
    elif config["use_blackbox"]:
        result = compile_p4_prog(BLACKBOX_BIN, p4_file, dump_dir)
    else:
        result = compile_p4_prog(WHITEBOX_BIN, p4_file, dump_dir)
    if result.returncode != util.EXIT_SUCCESS:
        if not is_known_bug(result):
            log.error("Failed to compile the P4 code!")
            log.error("Found a new bug!")
            dump_result(result, CRASH_BUG_DIR, p4_file)
            dump_file(CRASH_BUG_DIR, p4_file)
        # reset the dump directory
        util.del_dir(dump_dir)
        return result
    # check validation
    if config["do_validate"]:
        result = validate(dump_dir, p4_file, log_file, config)
    # reset the dump directory
    util.del_dir(dump_dir)
    return result
Esempio n. 17
0
def generate_analysis():

    parser = argparse.ArgumentParser()
    parser.add_argument("-i",
                        "--p4_input",
                        dest="p4_input",
                        default="p4c/testdata/p4_16_samples",
                        help="A P4 file or path to a "
                        "directory which contains P4 files.")
    # Parse options and process argv
    args = parser.parse_args()
    p4_input = args.p4_input
    if os.path.isfile(p4_input):
        pass_dir = "single_passes"
        util.check_dir(pass_dir)
        open(f"{pass_dir}/no_passes.txt", 'w+')
        analyse_p4_file(p4_input, pass_dir)
    else:
        pass_dir = "passes"
        util.check_dir(pass_dir)
        open(f"{pass_dir}/no_passes.txt", 'w+')
        for p4_file in glob.glob(f"{p4_input}/*.p4i"):
            analyse_p4_file(p4_file, pass_dir)
Esempio n. 18
0
def validate_translation(p4_file, target_dir, p4c_bin, allow_undef=False):
    log.info("\n\n" + "-" * 70)
    log.info("Analysing %s", p4_file)
    start_time = datetime.now()
    util.check_dir(target_dir)
    fail_dir = target_dir.joinpath("failed")
    # run the p4 compiler and dump all the passes for this file
    passes = gen_p4_passes(p4c_bin, target_dir, p4_file)
    passes = prune_passes(passes)
    p4_py_files = []
    # for each emitted pass, generate a python representation
    for p4_pass in passes:
        p4_path = Path(p4_pass).stem
        py_file = f"{target_dir}/{p4_path}.py"
        result = run_p4_to_py(p4_pass, py_file)
        if result.returncode != util.EXIT_SUCCESS:
            log.error("Failed to translate P4 to Python.")
            log.error("Compiler crashed!")
            util.check_dir(fail_dir)
            with open(f"{fail_dir}/error.txt", 'w+') as err_file:
                err_file.write(result.stderr.decode("utf-8"))
            util.copy_file([p4_pass, py_file], fail_dir)
            return result.returncode
        p4_py_files.append(f"{target_dir}/{p4_path}")
    if len(p4_py_files) < 2:
        log.warning("P4 file did not generate enough passes!")
        return util.EXIT_SKIPPED
    # perform the actual comparison
    result = z3check.z3_check(p4_py_files, fail_dir, allow_undef)
    done_time = datetime.now()
    elapsed = done_time - start_time
    time_str = time.strftime("%H hours %M minutes %S seconds",
                             time.gmtime(elapsed.total_seconds()))
    ms = elapsed.microseconds / 1000
    log.info("Translation validation took %s %s milliseconds.", time_str, ms)
    return result
def diff_files(passes, pass_dir, p4_file):

    p4_name = p4_file.name.stem
    for index, p4_pass in enumerate(passes[1:]):
        pass_before = passes[index - 1]
        pass_after = passes[index]
        diff_dir = f"{pass_dir}/{p4_name}"
        util.check_dir(diff_dir)
        diff_file = f"{diff_dir}/{p4_name}_{p4_pass}.diff"
        diff_cmd = "diff -rupP "
        diff_cmd += "--label=\"before_pass\" --label=\"after_pass\" "
        diff_cmd += f"{pass_before} {pass_after}"
        diff_cmd += f"> {diff_file}"
        log.debug("Creating a diff of the file")
        log.debug("Command: %s", diff_cmd)
        util.exec_process(diff_cmd)
        if os.stat(diff_file).st_size == 0:
            os.remove(diff_file)
        else:
            after_name = f"{diff_dir}/{p4_name}_{p4_pass}{p4_file.suffix}"
            util.copy_file(pass_after, after_name)
            og_name = f"{diff_dir}/{p4_name}_original{p4_file.suffix}"
            util.copy_file(p4_file, og_name)
    return util.EXIT_SUCCESS
def main(args):

    p4_input = Path(args.p4_input).resolve()
    pass_dir = Path(args.pass_dir)
    p4c_bin = args.p4c_bin
    allow_undef = args.allow_undef
    dunp_info = args.dunp_info
    if os.path.isfile(p4_input):
        pass_dir = pass_dir.joinpath(p4_input.stem)
        util.del_dir(pass_dir)
        result = validate_translation(p4_input, pass_dir, p4c_bin, allow_undef,
                                      dunp_info)
        sys.exit(result)
    elif os.path.isdir(p4_input):
        util.check_dir(pass_dir)
        for p4_file in list(p4_input.glob("**/*.p4")):
            output_dir = pass_dir.joinpath(p4_file.stem)
            util.del_dir(output_dir)
            validate_translation(p4_file, output_dir, p4c_bin, allow_undef)
        result = util.EXIT_SUCCESS
    else:
        log.error("Input file \"%s\" does not exist!", p4_input)
        result = util.EXIT_SUCCESS
    sys.exit(result)
Esempio n. 21
0
def main(args):
    # configure logging
    logging.basicConfig(filename=args.log_file,
                        format="%(levelname)s:%(message)s",
                        level=logging.INFO,
                        filemode='w')
    stderr_log = logging.StreamHandler()
    stderr_log.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))
    logging.getLogger().addHandler(stderr_log)

    if args.randomize_input:
        seed = int.from_bytes(os.getrandom(8), "big")
        z3.set_param(
            "smt.phase_selection",
            5,
            "smt.random_seed",
            seed,
            "smt.arith.random_initial_value",
            True,
            "sat.phase",
            "random",
        )

    config = {}
    config["arch"] = args.arch
    if config["arch"] == "tna":
        config["pipe_name"] = "pipe0_ingress"
        config["ingress_var"] = "ingress"
    elif config["arch"] == "v1model":
        config["pipe_name"] = "ig"
        config["ingress_var"] = "ig"
    elif config["arch"] == "psa":
        config["pipe_name"] = "ingress_ig"
        config["ingress_var"] = "ig"
    else:
        raise RuntimeError("Unsupported test arch \"%s\"!" % config["arch"])

    if args.p4_input:
        p4_input = Path(args.p4_input)
        out_base_dir = Path(args.out_dir)
    else:
        out_base_dir = Path(args.out_dir).joinpath("rnd_test")
        util.check_dir(out_base_dir)
        p4_input = out_base_dir.joinpath("rnd_test.p4")
        # generate a random program from scratch
        generate_p4_prog(P4RANDOM_BIN, p4_input, config)

    if os.path.isfile(p4_input):
        out_dir = out_base_dir.joinpath(p4_input.stem)
        util.del_dir(out_dir)
        config["out_dir"] = out_dir
        config["p4_input"] = p4_input
        result = perform_blackbox_test(config)
    else:
        util.check_dir(out_base_dir)
        for p4_file in list(p4_input.glob("**/*.p4")):
            out_dir = out_base_dir.joinpath(p4_file.stem)
            util.del_dir(out_dir)
            config["out_dir"] = out_dir
            config["p4_input"] = p4_file
            result = perform_blackbox_test(config)
    sys.exit(result)
Esempio n. 22
0
def dump_result(result, target_dir, p4_file):
    util.check_dir(target_dir)
    test_id = target_dir.joinpath(p4_file.stem)
    with open(f"{test_id}.err", 'w+') as err_file:
        err_file.write(result.stderr.decode("utf-8"))
Esempio n. 23
0
def prep_test(p4_name, p4_dir=P4_DIR):
    p4_file = p4_dir.joinpath(p4_name)
    target_dir = TARGET_DIR.joinpath(p4_file.stem)
    util.del_dir(target_dir)
    util.check_dir(target_dir)
    return p4_file, target_dir
Esempio n. 24
0
def perform_blackbox_test(config):
    out_dir = config["out_dir"]
    p4_input = config["p4_input"]
    if out_dir == OUT_DIR:
        out_dir = out_dir.joinpath(p4_input.stem)
    util.check_dir(out_dir)
    util.copy_file(p4_input, out_dir)
    config["out_dir"] = out_dir
    config["p4_input"] = p4_input

    # get the semantic representation of the original program
    z3_main_prog, result = get_semantics(config)
    if result != util.EXIT_SUCCESS:
        return result
    # now we actually verify that we can find an input
    s = z3.Solver()
    # we currently ignore all other pipelines and focus on the ingress pipeline
    main_formula = z3.simplify(z3_main_prog[config["pipe_name"]])
    # this util might come in handy later.
    # z3.z3util.get_vars(main_formula)
    conditions = get_branch_conditions(main_formula)
    log.info(conditions)
    cond_tuple = dissect_conds(config, conditions)
    permut_conds, avoid_conds, undefined_conds = cond_tuple
    log.info("Computing permutations...")
    # FIXME: This does not scale well...
    # FIXME: This is a complete hack, use a more sophisticated method
    permuts = [[f(var) for var, f in zip(permut_conds, x)]
               for x in itertools.product([z3.Not, lambda x: x],
                                          repeat=len(permut_conds))]
    output_const = z3.Const("output", main_formula.sort())
    # bind the output constant to the output of the main program
    s.add(main_formula == output_const)
    # all keys must be false for now
    # FIXME: Some of them should be usable
    log.info(15 * "#")
    log.info("Undefined conditions:")
    s.add(z3.And(*undefined_conds))
    for cond in undefined_conds:
        log.info(cond)
    log.info("Conditions to avoid:")
    s.add(z3.Not(z3.Or(*avoid_conds)))
    for cond in avoid_conds:
        log.info(cond)
    log.info("Permissible permutations:")
    for cond in permuts:
        log.info(cond)
    log.info(15 * "#")
    for permut in permuts:
        s.push()
        s.add(permut)
        log.info("Checking for solution...")
        ret = s.check()
        if ret == z3.sat:
            log.info("Found a solution!")
            # get the model
            m = s.model()
            # this does not work well yet... desperate hack
            # FIXME: Figure out a way to solve this, might not be solvable
            avoid_matches = z3.Not(z3.Or(*avoid_conds))
            undefined_matches = z3.And(*undefined_conds)
            permut_match = z3.And(*permut)
            g = z3.Goal()
            g.add(main_formula == output_const, avoid_matches,
                  undefined_matches, permut_match)
            log.debug(z3.tactics())
            t = z3.Then(z3.Tactic("propagate-values"),
                        z3.Tactic("ctx-solver-simplify"),
                        z3.Tactic("elim-and"))
            log.info("Inferring simplified input and output")
            constrained_output = t.apply(g)
            log.info("Inferring dont-care map...")
            output_var = constrained_output[0][0]
            dont_care_map = get_dont_care_map(config, output_var)
            result = check_with_stf(config, m, output_const, dont_care_map)
            if result != util.EXIT_SUCCESS:
                return result
        else:
            # FIXME: This should be an error
            log.warning("No valid input could be found!")
        s.pop()
    return result
Esempio n. 25
0
def handle_pyz3_error(fail_dir, p4_file):
    util.check_dir(fail_dir)
    failed = [p4_file.with_suffix(".py"), p4_file.with_suffix(".p4i")]
    util.copy_file(failed, fail_dir)
Esempio n. 26
0
def run_tofino_test(out_dir, p4_input, stf_file_name):
    # we need to change the working directory
    # tofino scripts make some assumptions where to dump files
    prog_name = p4_input.stem
    # we need to create a specific test dir in which we can run tests
    test_dir = out_dir.joinpath("test_dir")
    util.check_dir(test_dir)
    util.copy_file(stf_file_name, test_dir)
    template_name = test_dir.joinpath(f"{prog_name}.py")
    # use a test template that runs stf tests
    util.copy_file(f"{FILE_DIR}/tofino_test_template.py", template_name)

    # initialize the target install
    log.info("Building the tofino target...")
    config_cmd = f"{TOFINO_DIR}/pkgsrc/p4-build/configure "
    config_cmd += f"--with-tofino --with-p4c=bf-p4c "
    config_cmd += f"--prefix={TOFINO_DIR}/install "
    config_cmd += f"--bindir={TOFINO_DIR}/install/bin "
    config_cmd += f"P4_NAME={prog_name} "
    config_cmd += f"P4_PATH={p4_input.resolve()} "
    config_cmd += f"P4_VERSION=p4-16 "
    config_cmd += f"P4_ARCHITECTURE=tna "
    result = util.exec_process(config_cmd, cwd=out_dir)
    if result.returncode != util.EXIT_SUCCESS:
        return result, result.stdout, result.stderr
    # create the target
    make_cmd = f"make -C {out_dir} "
    result = util.exec_process(make_cmd)
    if result.returncode != util.EXIT_SUCCESS:
        return result, result.stdout, result.stderr
    # install the target in the tofino folder
    make_cmd = f"make install -C {out_dir} "
    result = util.exec_process(make_cmd)
    if result.returncode != util.EXIT_SUCCESS:
        return result, result.stdout, result.stderr
    procs = []
    test_proc = None
    # start the target in the background
    log.info("Starting the tofino model...")
    os_env = os.environ.copy()
    os_env["SDE"] = f"{TOFINO_DIR}"
    os_env["SDE_INSTALL"] = f"{TOFINO_DIR}/install"

    model_cmd = f"{TOFINO_DIR}/run_tofino_model.sh "
    model_cmd += f"-p {prog_name} "
    proc = util.start_process(model_cmd,
                              preexec_fn=os.setsid,
                              env=os_env,
                              cwd=out_dir)
    procs.append(proc)
    # start the binary for the target in the background
    log.info("Launching switchd...")
    os_env = os.environ.copy()
    os_env["SDE"] = f"{TOFINO_DIR}"
    os_env["SDE_INSTALL"] = f"{TOFINO_DIR}/install"

    switch_cmd = f"{TOFINO_DIR}/run_switchd.sh "
    switch_cmd += f"--arch tofino "
    switch_cmd += f"-p {prog_name} "
    proc = util.start_process(switch_cmd,
                              preexec_fn=os.setsid,
                              env=os_env,
                              cwd=out_dir)
    procs.append(proc)

    # wait for a bit
    time.sleep(2)
    # finally we can run the test
    log.info("Running the actual test...")
    test_cmd = f"{TOFINO_DIR}/run_p4_tests.sh "
    test_cmd += f"-t {test_dir} "
    os_env = os.environ.copy()
    os_env["SDE"] = f"{TOFINO_DIR}"
    os_env["SDE_INSTALL"] = f"{TOFINO_DIR}/install"
    os_env["PYTHONPATH"] = f"${{PYTHONPATH}}:{FILE_DIR}"
    test_proc = util.start_process(test_cmd, env=os_env, cwd=out_dir)

    def signal_handler(sig, frame):
        log.warning("run_tofino_test: Caught Interrupt, exiting...")
        cleanup(procs)
        os.kill(test_proc.pid, signal.SIGINT)
        os.kill(test_proc.pid, signal.SIGTERM)
        sys.exit(1)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    stdout, stderr = test_proc.communicate()
    cleanup(procs)
    return test_proc, stdout, stderr