def analyze_file(rst_file): analysis_error = False with open(rst_file) as f: content = f.read() blocks = list( enumerate( filter( lambda b: b.language in ["ada", "c"] if isinstance(b, CodeBlock) else True, Block.get_blocks(content)))) code_blocks = [(i, b) for i, b in blocks if isinstance(b, CodeBlock)] def run(*run_args): if args.verbose: print("Running \"{}\"".format(" ".join(run_args))) try: output = S.check_output(run_args, stderr=S.STDOUT).decode("utf-8") all_output.extend(output.splitlines()) except S.CalledProcessError as e: all_output.extend(e.output.decode("utf-8").splitlines()) raise e return output if args.code_block_at: for i, block in code_blocks: block.run = False if block.line_start < args.code_block_at < block.line_end: block.run = True if args.code_block: expr = "code_blocks[{}]".format(args.code_block) subset = eval(expr, globals(), locals()) if not isinstance(code_blocks, list): subset = [subset] for i, code_block in code_blocks: code_block.run = False for i, code_block in subset: code_block.run = True def extract_diagnostics(lines): diags = [] r = re.compile("(.+?):(\d+):(\d+): (.+)") for l in lines: m = r.match(l) if m: f, l, c, t = m.groups() diags.append(Diag(f, int(l), int(c), t)) return diags def remove_string(some_text, rem): return re.sub(".*" + rem + ".*\n?", "", some_text) projects = dict() for (i, b) in code_blocks: if b.project is None: print("Error: project not set in {} at line {}".format( rst_file, str(b.line_start))) exit(1) if not b.project in projects: projects[b.project] = list() projects[b.project].append((i, b)) work_dir = os.getcwd() base_project_dir = "projects" for project in projects: def init_project_dir(project): project_dir = base_project_dir + "/" + project.replace(".", "/") if os.path.exists(project_dir): shutil.rmtree(project_dir) try: os.makedirs(project_dir) except OSError as e: if e.errno != errno.EEXIST: raise os.chdir(project_dir) init_project_dir(project) if args.verbose: print(header("Checking project {}".format(project))) print("Number of code blocks: {}".format(len(projects[project]))) for i, block in projects[project]: if isinstance(block, ConfigBlock): current_config.update(block) continue has_error = False loc = "at {}:{} (code block #{})".format(rst_file, block.line_start, i) all_output = [] def print_diags(): diags = extract_diagnostics(all_output) for diag in diags: diag.line = diag.line + block.line_start diag.file = rst_file print(diag) def print_error(*error_args): error(*error_args) print_diags() no_check = any(sphinx_class in ["ada-nocheck", "c-nocheck"] for sphinx_class in block.classes) if no_check: if args.verbose: print("Skipping code block {}".format(loc)) continue if args.verbose: print(header("Checking code block {}".format(loc))) split = block.text.splitlines() source_files = list() if block.manual_chop: source_files = manual_chop(split) else: source_files = real_gnatchop(split) if len(source_files) == 0: print_error(loc, "Failed to chop example, skipping\n") analysis_error = True continue for source_file in source_files: with open(source_file.basename, u"w") as code_file: code_file.write(source_file.content) try: if block.language == "ada": out = run("gcc", "-c", "-gnats", "-gnatyg0-s", source_file.basename) elif block.language == "c": out = run("gcc", "-c", source_file.basename) if out: print_error(loc, "Failed to syntax check example") has_error = True except S.CalledProcessError: print_error(loc, "Failed to syntax check example") has_error = True if 'ada-syntax-only' in block.classes or not block.run: continue compile_error = False prove_error = False is_prove_error_class = False prove_buttons = [ "prove", "prove_flow", "prove_flow_report_all", "prove_report_all" ] def get_main_filename(block): if block.main_file is not None: main_file = block.main_file else: main_file = source_files[-1].basename return main_file def make_project_block_dir(): project_block_dir = str(block.line_start) if not os.path.exists(project_block_dir): os.makedirs(project_block_dir) return project_block_dir if (('ada-run' in block.classes or 'ada-run-expect-failure' in block.classes or 'run' in block.buttons) and not 'ada-norun' in block.classes): main_file = get_main_filename(block) project_block_dir = make_project_block_dir() if block.language == "ada": try: out = run("gprbuild", "-gnata", "-gnatyg0-s", "-f", main_file) except S.CalledProcessError as e: if 'ada-expect-compile-error' in block.classes: compile_error = True else: print_error(loc, "Failed to compile example") print(e.output) has_error = True out = str(e.output.decode("utf-8")) out = remove_string(out, "using project") with open(project_block_dir + "/build.log", u"w") as logfile: logfile.write(out) elif block.language == "c": try: cmd = ["gcc", "-o", P.splitext(main_file)[0]] + glob.glob('*.c') out = run(*cmd) except S.CalledProcessError as e: if 'c-expect-compile-error' in block.classes: compile_error = True else: print_error(loc, "Failed to compile example") print(e.output) has_error = True out = str(e.output.decode("utf-8")) with open(project_block_dir + "/build.log", u"w") as logfile: logfile.write(out) if not compile_error and not has_error: if block.language == "ada": try: out = run("./{}".format(P.splitext(main_file)[0])) if 'ada-run-expect-failure' in block.classes: print_error( loc, "Running of example should have failed") has_error = True except S.CalledProcessError as e: if 'ada-run-expect-failure' in block.classes: if args.verbose: print( "Running of example expectedly failed") else: print_error(loc, "Running of example failed") has_error = True out = str(e.output.decode("utf-8")) with open(project_block_dir + "/run.log", u"w") as logfile: logfile.write(out) elif block.language == "c": try: out = run("./{}".format(P.splitext(main_file)[0])) if 'c-run-expect-failure' in block.classes: print_error( loc, "Running of example should have failed") has_error = True except S.CalledProcessError as e: if 'c-run-expect-failure' in block.classes: if args.verbose: print( "Running of example expectedly failed") else: print_error(loc, "Running of example failed") has_error = True out = str(e.output.decode("utf-8")) with open(project_block_dir + "/run.log", u"w") as logfile: logfile.write(out) if 'compile' in block.buttons: project_block_dir = make_project_block_dir() for source_file in source_files: if block.language == "ada": try: out = run("gcc", "-c", "-gnatc", "-gnatyg0-s", source_file.basename) except S.CalledProcessError as e: if 'ada-expect-compile-error' in block.classes: compile_error = True else: print_error(loc, "Failed to compile example") has_error = True out = str(e.output.decode("utf-8")) with open(project_block_dir + "/compile.log", u"w+") as logfile: logfile.write(out) elif block.language == "c": try: out = run("gcc", "-c", source_file.basename) except S.CalledProcessError as e: if 'c-expect-compile-error' in block.classes: compile_error = True else: print_error(loc, "Failed to compile example") has_error = True out = str(e.output.decode("utf-8")) with open(project_block_dir + "/compile.log", u"w+") as logfile: logfile.write(out) if any(b in prove_buttons for b in block.buttons): if block.language == "ada": project_block_dir = make_project_block_dir() main_file = get_main_filename(block) spark_mode = True project_filename = write_project_file( main_file, block.compiler_switches, spark_mode) is_prove_error_class = any(c in [ 'ada-expect-prove-error', 'ada-expect-compile-error', 'ada-run-expect-failure' ] for c in block.classes) extra_args = [] if 'prove_flow' in block.buttons: extra_args = ["--mode=flow"] elif 'prove_flow_report_all' in block.buttons: extra_args = ["--mode=flow", "--report=all"] elif 'prove_report_all' in block.buttons: extra_args = ["--report=all"] line = [ "gnatprove", "-P", project_filename, "--checks-as-errors", "--level=0", "--no-axiom-guard" ] line.extend(extra_args) try: out = run(*line) except S.CalledProcessError as e: if is_prove_error_class: prove_error = True else: print_error(loc, "Failed to prove example") print(e.output) has_error = True out = str(e.output.decode("utf-8")) out = remove_string(out, "Summary logged in") with open(project_block_dir + "/prove.log", u"w") as logfile: logfile.write(out) else: print_error(loc, "Wrong language selected for prove button") print(e.output) has_error = True if len(block.buttons) == 0: print_error( loc, "Expected at least 'no_button' indicator, got none!") has_error = True if 'ada-expect-compile-error' in block.classes: if not any(b in ['compile', 'run'] for b in block.buttons): print_error(loc, "Expected compile or run button, got none!") has_error = True if not compile_error: print_error(loc, "Expected compile error, got none!") has_error = True if 'ada-expect-prove-error' in block.classes: if not any(b in prove_buttons for b in block.buttons): print_error(loc, "Expected prove button, got none!") has_error = True if any(b in prove_buttons for b in block.buttons): if is_prove_error_class and not prove_error: print_error(loc, "Expected prove error, got none!") has_error = True if (any(c in ['ada-run-expect-failure', 'ada-norun'] for c in block.classes) and not 'run' in block.buttons): print_error(loc, "Expected run button, got none!") has_error = True if has_error: analysis_error = True elif args.verbose: print(C.col("SUCCESS", C.Colors.GREEN)) if args.all_diagnostics: print_diags() os.chdir(work_dir) if os.path.exists(base_project_dir) and not args.keep_files: shutil.rmtree(base_project_dir) return analysis_error
def get_blocks(input_text): lang_re = re.compile("\s*.. code::\s*(\w+)?\s*") project_re = re.compile("\s*.. code::.*project=(\S+)?") main_re = re.compile("\s*.. code::.*main=(\S+)?") manual_chop_re = re.compile("\s*.. code::.*manual_chop?") button_re = re.compile("\s+(\S+)_button") code_config_re = re.compile(":code-config:`(.*)?`") classes_re = re.compile("\s*:class:\s*(.+)") switches_re = re.compile("\s*.. code::.*switches=(\S+)?") compiler_switches_re = re.compile("Compiler[(](\S+)?[)]") blocks = [] lines = input_text.splitlines() def first_nonws(line): for i, c in enumerate(line): if not c.isspace(): return i return 0 indents = map(first_nonws, lines) classes = [] compiler_switches = [] buttons = [] cb_start = -1 cb_indent = -1 lang = "" project = None main_file = None manual_chop = None last_line_number = -1 def is_empty(line): return (not line) or line.isspace() def process_block(i, line, indent): nonlocal classes, cb_start, cb_indent, lang if cb_indent == -1 and not is_empty(line): cb_indent = indent if indent < cb_indent and not is_empty(line): blocks.append( CodeBlock( cb_start, i, "\n".join(l[cb_indent:] for l in lines[cb_start:i]), lang, project, main_file, compiler_switches, classes, manual_chop, buttons)) classes, cb_start, cb_indent, lang = [], -1, -1, "" m = classes_re.match(line) if m: classes = [str.strip(l) for l in m.groups()[0].split(",")] cb_start = i + 1 def start_code_block(i, line, indent): nonlocal cb_start, lang, project, main_file, manual_chop, \ buttons, compiler_switches cb_start, lang = (i + 1, lang_re.match(line).groups()[0]) project = project_re.match(line) if project is not None: project = project.groups()[0] main_file = main_re.match(line) if main_file is not None: # Retrieve actual main filename main_file = main_file.groups()[0] if lang == "c": manual_chop = True else: manual_chop = (manual_chop_re.match(line) is not None) buttons = button_re.findall(line) all_switches = switches_re.match(line) compiler_switches = [] if all_switches is not None: all_switches = all_switches.groups()[0] compiler_switches = compiler_switches_re.match(all_switches) if compiler_switches is not None: compiler_switches = [ str.strip(l) for l in compiler_switches.groups()[0].split(",") ] def start_config_block(i, line, indent): blocks.append( ConfigBlock(**dict( kv.split('=') for kv in code_config_re.findall(line)[0].split(";")))) for i, (line, indent) in enumerate(zip(lines, indents)): last_line_number = i if cb_start != -1: process_block(i, line, indent) else: if line[indent:].startswith(".. code::"): start_code_block(i, line, indent) elif line[indent:].startswith(":code-config:"): start_config_block(i, line, indent) if cb_start != -1: print( "{}: code block (start: {}, project: {}) doesn't have explanatory section!" .format(C.col("WARNING", C.Colors.YELLOW), cb_start, project)) process_block(last_line_number + 1, "END", 0) # Error: unable to process last code block if cb_start != -1: print( "{}: code block (start: {}, project: {}) hasn't been successfully processed!" .format(C.col("ERROR", C.Colors.RED), cb_start, project)) exit(1) return blocks
def error(loc, strn): print("{} {}: {}".format(C.col("ERROR", C.Colors.RED), loc, strn))
def header(strn): return C.col("{}\n{}\n".format(strn, '*' * len(strn)), C.Colors.BLUE)
if os.path.exists(base_project_dir) and not args.keep_files: shutil.rmtree(base_project_dir) return analysis_error # Remove the build dir, but only if the user didn't ask for a specific # subset of code_blocks if (os.path.exists(args.build_dir) and not args.code_block and not args.keep_files): shutil.rmtree(args.build_dir) if not os.path.exists(args.build_dir): os.makedirs(args.build_dir) os.chdir(args.build_dir) test_error = False for f in args.rst_files: analysis_error = analyze_file(f) if analysis_error: test_error = True if test_error: print(C.col("TEST ERROR", C.Colors.RED)) exit(1) elif args.verbose: print(C.col("TEST SUCCESS", C.Colors.GREEN))
def analyze_file(rst_file): with open(rst_file) as f: content = f.read() blocks = list( enumerate( filter( lambda b: b.language == "ada" if isinstance(b, CodeBlock) else True, Block.get_blocks(content)))) code_blocks = [(i, b) for i, b in blocks if isinstance(b, CodeBlock)] def run(*run_args): if args.verbose: print "Running \"{}\"".format(" ".join(run_args)) try: output = S.check_output(run_args, stderr=S.STDOUT) all_output.extend(output.splitlines()) except S.CalledProcessError as e: all_output.extend(e.output.splitlines()) raise e return output if args.code_block_at: for i, block in code_blocks: block.run = False if block.line_start < args.code_block_at < block.line_end: block.run = True if args.code_block: expr = "code_blocks[{}]".format(args.code_block) subset = eval(expr, globals(), locals()) if not isinstance(code_blocks, list): subset = [subset] for i, code_block in code_blocks: code_block.run = False for i, code_block in subset: code_block.run = True def extract_diagnostics(lines): diags = [] r = re.compile("(.+?):(\d+):(\d+): (.+)") for l in lines: m = r.match(l) if m: f, l, c, t = m.groups() diags.append(Diag(f, int(l), int(c), t)) return diags for i, block in blocks: if isinstance(block, ConfigBlock): current_config.update(block) continue has_error = False loc = "at {}:{} (code block #{})".format(rst_file, block.line_start, i) all_output = [] def print_diags(): diags = extract_diagnostics(all_output) for diag in diags: diag.line = diag.line + block.line_start diag.file = rst_file print diag def print_error(*error_args): error(*error_args) print_diags() if 'ada-nocheck' in block.classes: if args.verbose: print "Skipping code block {}".format(loc) continue if args.verbose: print header("Checking code block {}".format(loc)) with open(u"code.ada", u"w") as code_file: code_file.write(block.text) try: out = run("gnatchop", "-r", "-w", "code.ada").splitlines() except S.CalledProcessError: print_error(loc, "Failed to chop example, skipping\n") continue idx = -1 for i, line in enumerate(out): if line.endswith("into:"): idx = i + 1 break if idx == -1: print_error(loc, "Failed to chop example, skipping\n") continue source_files = [s.strip() for s in out[idx:]] for source_file in source_files: try: out = run("gcc", "-c", "-gnats", "-gnatyg0-s", source_file) except S.CalledProcessError: print_error(loc, "Failed to syntax check example") has_error = True if out: print_error(loc, "Failed to syntax check example") has_error = True if 'ada-syntax-only' in block.classes or not block.run: continue compile_error = False if ('ada-run' in block.classes or 'ada-run-expect-failure' in block.classes): if len(source_files) == 1: main_file = source_files[0] else: main_file = 'main.adb' try: out = run("gprbuild", "-gnata", "-gnatyg0-s", "-f", main_file) except S.CalledProcessError as e: print_error(loc, "Failed to compile example") print e.output has_error = True if not has_error: try: run("./{}".format(P.splitext(main_file)[0])) if 'ada-run-expect-failure' in block.classes: print_error(loc, "Running of example should have failed") has_error = True except S.CalledProcessError: if 'ada-run-expect-failure' in block.classes: if args.verbose: print "Running of example expectedly failed" else: print_error(loc, "Running of example failed") has_error = True else: for source_file in source_files: try: run("gcc", "-c", "-gnatc", "-gnatyg0-s", source_file) except S.CalledProcessError: if 'ada-expect-compile-error' in block.classes: compile_error = True else: print_error(loc, "Failed to compile example") has_error = True if 'ada-expect-compile-error' in block.classes and not compile_error: print_error(loc, "Expected compile error, got none!") has_error = True if not has_error and args.verbose: print C.col("SUCCESS", C.Colors.GREEN) if args.all_diagnostics: print_diags() if source_files and not current_config.accumulate_code: for source_file in source_files: os.remove(source_file)
has_error = True except S.CalledProcessError: if 'ada-run-expect-failure' in code_block.classes: if args.verbose: print "Running of example expectedly failed" else: print_error(loc, "Running of example failed") has_error = True else: for source_file in source_files: try: run("gcc", "-c", "-gnatc", "-gnaty", source_file) except S.CalledProcessError: if 'ada-expect-compile-error' in code_block.classes: compile_error = True else: print_error(loc, "Failed to compile example") has_error = True if 'ada-expect-compile-error' in code_block.classes and not compile_error: print_error(loc, "Expected compile error, got none!") has_error = True if not has_error and args.verbose: print C.col("SUCCESS", C.Colors.GREEN) if args.all_diagnostics: print_diags()