Changed in v4: * Fix SARIF schema URL * Various changes to help with API docs
Changed in v3: * split out the C and C++ API tests into this patch * heavily rewritten libdiagnostics.exp; added support for Python tests * tests updated for API changes, rewritten and extended gcc/testsuite/ChangeLog: * libdiagnostics.dg/libdiagnostics.exp: New, adapted from jit.exp. * libdiagnostics.dg/sarif.py: New. * libdiagnostics.dg/test-dump.c: New test. * libdiagnostics.dg/test-error-c.py: New test. * libdiagnostics.dg/test-error-with-note-c.py: New test. * libdiagnostics.dg/test-error-with-note.c: New test. * libdiagnostics.dg/test-error-with-note.cc: New test. * libdiagnostics.dg/test-error.c: New test. * libdiagnostics.dg/test-error.cc: New test. * libdiagnostics.dg/test-example-1.c: New test. * libdiagnostics.dg/test-fix-it-hint-c.py: New test. * libdiagnostics.dg/test-fix-it-hint.c: New test. * libdiagnostics.dg/test-fix-it-hint.cc: New test. * libdiagnostics.dg/test-helpers++.h: New test. * libdiagnostics.dg/test-helpers.h: New test. * libdiagnostics.dg/test-labelled-ranges.c: New test. * libdiagnostics.dg/test-labelled-ranges.cc: New test. * libdiagnostics.dg/test-labelled-ranges.py: New test. * libdiagnostics.dg/test-logical-location-c.py: New test. * libdiagnostics.dg/test-logical-location.c: New test. * libdiagnostics.dg/test-metadata-c.py: New test. * libdiagnostics.dg/test-metadata.c: New test. * libdiagnostics.dg/test-multiple-lines-c.py: New test. * libdiagnostics.dg/test-multiple-lines.c: New test. * libdiagnostics.dg/test-no-column-c.py: New test. * libdiagnostics.dg/test-no-column.c: New test. * libdiagnostics.dg/test-no-diagnostics-c.py: New test. * libdiagnostics.dg/test-no-diagnostics.c: New test. * libdiagnostics.dg/test-note-with-fix-it-hint-c.py: New test. * libdiagnostics.dg/test-note-with-fix-it-hint.c: New test. * libdiagnostics.dg/test-text-sink-options.c: New test. * libdiagnostics.dg/test-warning-c.py: New test. * libdiagnostics.dg/test-warning-with-path-c.py: New test. * libdiagnostics.dg/test-warning-with-path.c: New test. * libdiagnostics.dg/test-warning.c: New test. * libdiagnostics.dg/test-write-sarif-to-file-c.py: New test. * libdiagnostics.dg/test-write-sarif-to-file.c: New test. * libdiagnostics.dg/test-write-text-to-file.c: New test. Signed-off-by: David Malcolm <dmalc...@redhat.com> --- .../libdiagnostics.dg/libdiagnostics.exp | 296 ++++++++++++++++++ gcc/testsuite/libdiagnostics.dg/sarif.py | 23 ++ gcc/testsuite/libdiagnostics.dg/test-dump.c | 69 ++++ .../libdiagnostics.dg/test-error-c.py | 54 ++++ .../test-error-with-note-c.py | 50 +++ .../libdiagnostics.dg/test-error-with-note.c | 76 +++++ .../libdiagnostics.dg/test-error-with-note.cc | 55 ++++ gcc/testsuite/libdiagnostics.dg/test-error.c | 61 ++++ gcc/testsuite/libdiagnostics.dg/test-error.cc | 47 +++ .../libdiagnostics.dg/test-example-1.c | 43 +++ .../libdiagnostics.dg/test-fix-it-hint-c.py | 46 +++ .../libdiagnostics.dg/test-fix-it-hint.c | 83 +++++ .../libdiagnostics.dg/test-fix-it-hint.cc | 74 +++++ .../libdiagnostics.dg/test-helpers++.h | 28 ++ .../libdiagnostics.dg/test-helpers.h | 72 +++++ .../libdiagnostics.dg/test-labelled-ranges.c | 71 +++++ .../libdiagnostics.dg/test-labelled-ranges.cc | 64 ++++ .../libdiagnostics.dg/test-labelled-ranges.py | 48 +++ .../test-logical-location-c.py | 37 +++ .../libdiagnostics.dg/test-logical-location.c | 81 +++++ .../libdiagnostics.dg/test-metadata-c.py | 45 +++ .../libdiagnostics.dg/test-metadata.c | 61 ++++ .../test-multiple-lines-c.py | 83 +++++ .../libdiagnostics.dg/test-multiple-lines.c | 78 +++++ .../libdiagnostics.dg/test-no-column-c.py | 35 +++ .../libdiagnostics.dg/test-no-column.c | 54 ++++ .../test-no-diagnostics-c.py | 42 +++ .../libdiagnostics.dg/test-no-diagnostics.c | 25 ++ .../test-note-with-fix-it-hint-c.py | 54 ++++ .../test-note-with-fix-it-hint.c | 69 ++++ .../test-text-sink-options.c | 59 ++++ .../libdiagnostics.dg/test-warning-c.py | 54 ++++ .../test-warning-with-path-c.py | 108 +++++++ .../test-warning-with-path.c | 138 ++++++++ .../libdiagnostics.dg/test-warning.c | 67 ++++ .../test-write-sarif-to-file-c.py | 55 ++++ .../test-write-sarif-to-file.c | 55 ++++ .../test-write-text-to-file.c | 47 +++ 38 files changed, 2507 insertions(+) create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp create mode 100644 gcc/testsuite/libdiagnostics.dg/sarif.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-dump.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-example-1.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers++.h create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp new file mode 100644 index 000000000000..d29a469ae4f8 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp @@ -0,0 +1,296 @@ +# Test code for libdiagnostics.so +# +# We will compile each of libdiagnostics.dg/test-*.{c,cc} into an executable +# dynamically linked against libdiagnostics.so, and then run each +# such executable. +# +# These executables call into the libdiagnostics.so API to emit diagnostics, +# sometimes in text form, and other times in SARIF form. + +# Kludge alert: +# We need g++_init so that it can find the stdlib include path. +# +# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper, +# which normally comes from the definition of +# ${tool}_maybe_build_wrapper within lib/wrapper.exp. +# +# However, for us, ${tool} is "libdiagnostics". +# Hence we load wrapper.exp with tool == "g++", so that +# g++_maybe_build_wrapper is defined. +set tool g++ +load_lib wrapper.exp +set tool libdiagnostics + +load_lib dg.exp +load_lib prune.exp +load_lib target-supports.exp +load_lib gcc-defs.exp +load_lib timeout.exp +load_lib target-libpath.exp +load_lib gcc.exp +load_lib g++.exp +load_lib dejagnu.exp +load_lib target-supports-dg.exp +load_lib valgrind.exp +load_lib scansarif.exp +load_lib dg-test-cleanup.exp + +# The default do-what keyword. +set dg-do-what-default compile + +# Adapted from jit.exp. +# +# Execute the executable file. +# Returns: +# A "" (empty) string if everything worked, or an error message +# if there was a problem. +# +proc fixed_host_execute {args} { + global env + global text + global spawn_id + + verbose "fixed_host_execute: $args" + + set timeoutmsg "Timed out: Never got started, " + set timeout 100 + set file all + set timetol 0 + set arguments "" + + if { [llength $args] == 0} { + set executable $args + } else { + set executable [lindex $args 0] + set params [lindex $args 1] + } + + verbose "The executable is $executable" 2 + if {![file exists ${executable}]} { + perror "The executable, \"$executable\" is missing" 0 + return "No source file found" + } elseif {![file executable ${executable}]} { + perror "The executable, \"$executable\" is not usable" 0 + return "Bad executable found" + } + + verbose "params: $params" 2 + + # spawn the executable and look for the DejaGnu output messages from the + # test case. + # spawn -noecho -open [open "|./${executable}" "r"] + + # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment. + # Note that it's best to configure gcc with --enable-valgrind-annotations + # when testing under valgrind. + set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)] + if $run_under_valgrind { + set valgrind_logfile "${executable}.valgrind.txt" + set valgrind_params {"valgrind"} + lappend valgrind_params "--leak-check=full" + lappend valgrind_params "--log-file=${valgrind_logfile}" + } else { + set valgrind_params {} + } + verbose "valgrind_params: $valgrind_params" 2 + + set args ${valgrind_params} + lappend args "./${executable}" + set args [concat $args ${params}] + verbose "args: $args" 2 + + set status [catch "exec -keepnewline $args" exe_output] + verbose "Test program returned $exe_output" 2 + + if $run_under_valgrind { + upvar 2 name name + parse_valgrind_logfile $name $valgrind_logfile fail + } + + # We don't do prune_gcc_output here, as we want + # to check *exactly* what we get from libdiagnostics + + return $exe_output +} + +# (end of code from dejagnu.exp) + +# GCC_UNDER_TEST is needed by gcc_target_compile +global GCC_UNDER_TEST +if ![info exists GCC_UNDER_TEST] { + set GCC_UNDER_TEST "[find_gcc]" +} + +g++_init + +# Initialize dg. +dg-init + +# Gather a list of all tests. + +# C and C++ tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.{c,c++} +set c_tests [find $srcdir/$subdir test-*.c] +set cxx_tests [find $srcdir/$subdir test-*.cc] +set tests [concat $c_tests $cxx_tests] + +verbose "tests: $tests" + +# Expand "SRCDIR" within ARG to the location of the top-level +# src directory + +proc diagnostics-expand-vars {arg} { + verbose "diagnostics-expand-vars: $arg" + global srcdir + verbose " srcdir: $srcdir" + # "srcdir" is that of the gcc/testsuite directory, so + # we need to go up two levels. + set arg [string map [list "SRCDIR" $srcdir/../..] $arg] + verbose " new arg: $arg" + return $arg +} + +# Parameters used when invoking the executables built from the test cases. + +global diagnostics-exe-params +set diagnostics-exe-params {} + +# Set "diagnostics-exe-params", expanding "SRCDIR" in each arg to the location of +# the top-level srcdir. + +proc dg-diagnostics-set-exe-params { args } { + verbose "dg-diagnostics-set-exe-params: $args" + + global diagnostics-exe-params + set diagnostics-exe-params {} + # Skip initial arg (line number) + foreach arg [lrange $args 1 [llength $args] ] { + lappend diagnostics-exe-params [diagnostics-expand-vars $arg] + } +} + +proc libdiagnostics-dg-test { prog do_what extra_tool_flags } { + verbose "within libdiagnostics-dg-test..." + verbose " prog: $prog" + verbose " do_what: $do_what" + verbose " extra_tool_flags: $extra_tool_flags" + + global dg-do-what-default + set dg-do-what [list ${dg-do-what-default} "" P] + + # If we're not supposed to try this test on this target, we're done. + if { [lindex ${dg-do-what} 1] == "N" } { + unsupported "$name" + verbose "$name not supported on this target, skipping it" 3 + return + } + + # Determine what to name the built executable. + # + # We simply append .exe to the filename, e.g. + # "test-foo.c.exe" + # since some testcases exist in both + # "test-foo.c" and + # "test-foo.cc" + # variants, and we don't want them to clobber each other's + # executables. + # + # This also ensures that the source name makes it into the + # pass/fail output, so that we can distinguish e.g. which test-foo + # is failing. + set output_file "[file tail $prog].exe" + verbose "output_file: $output_file" + + # Create the test executable: + set extension [file extension $prog] + if {$extension == ".cc"} { + set compilation_function "g++_target_compile" + } else { + set compilation_function "gcc_target_compile" + } + set options "{additional_flags=$extra_tool_flags}" + verbose "compilation_function=$compilation_function" + verbose "options=$options" + + set comp_output [$compilation_function $prog $output_file \ + "executable" $options] + upvar 1 name name + if ![libdiagnostics_check_compile "$name" "initial compilation" \ + $output_file $comp_output] then { + return + } + + # Run the test executable. + + # We need to set LD_LIBRARY_PATH so that the test files can find + # libdiagnostics.so + # Do this using set_ld_library_path_env_vars from target-libpath.exp + # We will restore the old value later using + # restore_ld_library_path_env_vars. + + # Unfortunately this API only supports a single saved value, rather + # than a stack, and g++_init has already called into this API, + # injecting the appropriate value for LD_LIBRARY_PATH for finding + # the built copy of libstdc++. + # Hence the call to restore_ld_library_path_env_vars would restore + # the *initial* value of LD_LIBRARY_PATH, and attempts to run + # a C++ testcase after running any prior testcases would thus look + # in the wrong place for libstdc++. This led to failures at startup + # of the form: + # ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe) + # when the built libstdc++ is more recent that the system libstdc++. + # + # As a workaround, reset the variable "orig_environment_saved" within + # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars + # API saves/restores the current value of LD_LIBRARY_PATH (as set up + # by g++_init). + global orig_environment_saved + set orig_environment_saved 0 + + global ld_library_path + global base_dir + set ld_library_path "$base_dir/../../" + set_ld_library_path_env_vars + + global diagnostics-exe-params + set args ${diagnostics-exe-params} + set diagnostics-exe-params {} + + set exe_output [fixed_host_execute $output_file $args ] + verbose "exe_output: $exe_output" + + restore_ld_library_path_env_vars + + # Analyze the output from the executable. To some what extent this + # is duplicating prune_gcc_output, but we're looking for *precise* + # output, so we can't reuse prune_gcc_output. + + global testname_with_flags + set testname_with_flags $name + + # Handle any freeform regexps. + set exe_output [handle-dg-regexps $exe_output] + + # Call into multiline.exp to handle any multiline output directives. + set exe_output [handle-multiline-outputs $exe_output] + + # Normally we would return $exe_output and $output_file to the + # caller, which would delete $output_file, the generated executable. + # If we need to debug, it's handy to be able to suppress this behavior, + # keeping the executable around. + + global env + set preserve_executables [info exists env(PRESERVE_EXECUTABLES)] + if $preserve_executables { + set output_file "" + } + + return [list $exe_output $output_file] +} + +set DEFAULT_CFLAGS "-I$srcdir/.. -ldiagnostics -g -Wall -Werror" + +# Main loop. This will invoke jig-dg-test on each test-*.c file. +dg-runtest $tests "" $DEFAULT_CFLAGS + +# All done. +dg-finish diff --git a/gcc/testsuite/libdiagnostics.dg/sarif.py b/gcc/testsuite/libdiagnostics.dg/sarif.py new file mode 100644 index 000000000000..7daf35b58190 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/sarif.py @@ -0,0 +1,23 @@ +import json +import os + +def sarif_from_env(): + # return parsed JSON content a SARIF_PATH file + json_filename = os.environ['SARIF_PATH'] + json_filename += '.sarif' + print('json_filename: %r' % json_filename) + with open(json_filename) as f: + json_data = f.read() + return json.loads(json_data) + +def get_location_artifact_uri(location): + return location['physicalLocation']['artifactLocation']['uri'] + +def get_location_physical_region(location): + return location['physicalLocation']['region'] + +def get_location_snippet_text(location): + return location['physicalLocation']['contextRegion']['snippet']['text'] + +def get_location_relationships(location): + return location['relationships'] diff --git a/gcc/testsuite/libdiagnostics.dg/test-dump.c b/gcc/testsuite/libdiagnostics.dg/test-dump.c new file mode 100644 index 000000000000..9f0576d5cd3e --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-dump.c @@ -0,0 +1,69 @@ +/* Usage example of dump API. */ + +#include "libdiagnostics.h" + +const int line_num = 42; + +int +main () +{ + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, "foo.c", "c"); + + fprintf (stderr, "file: "); + diagnostic_manager_debug_dump_file (diag_mgr, file, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +file: file(name="foo.c", sarif_source_language="c") + { dg-end-multiline-output "" } */ + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + fprintf (stderr, "loc_start: "); + diagnostic_manager_debug_dump_location (diag_mgr, loc_start, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +loc_start: foo.c:42:8: + { dg-end-multiline-output "" } */ + + fprintf (stderr, "loc_end: "); + diagnostic_manager_debug_dump_location (diag_mgr, loc_end, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +loc_end: foo.c:42:19: + { dg-end-multiline-output "" } */ + + fprintf (stderr, "loc_range: "); + diagnostic_manager_debug_dump_location (diag_mgr, loc_range, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +loc_range: foo.c:42:8: + { dg-end-multiline-output "" } */ + + const diagnostic_logical_location *logical_loc + = diagnostic_manager_new_logical_location (diag_mgr, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION, + NULL, /* parent */ + "test_short_name", + "test_qualified_name", + "test_decorated_name"); + + fprintf (stderr, "logical_loc: "); + diagnostic_manager_debug_dump_logical_location (diag_mgr, logical_loc, stderr); + fprintf (stderr, "\n"); + /* { dg-begin-multiline-output "" } +logical_loc: logical_location(kind=function, short_name="test_short_name", fully_qualified_name="test_qualified_name", decorated_name="test_decorated_name") + { dg-end-multiline-output "" } */ + + diagnostic_manager_release (diag_mgr); + return 0; +}; diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-c.py b/gcc/testsuite/libdiagnostics.dg/test-error-c.py new file mode 100644 index 000000000000..1206be80fb4a --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-c.py @@ -0,0 +1,54 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 17 +expected_file_name = 'test-error.c' + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == False + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'].endswith(expected_file_name) + assert artifacts[0]['sourceLanguage'] == 'c' + assert '#include <foo.h>' in artifacts[0]['contents']['text'] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "can't find 'foo.h'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name) + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 11 + assert phys_loc['region']['endColumn'] == 16 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == '#include <foo.h>\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py b/gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py new file mode 100644 index 000000000000..bed21caec74f --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note-c.py @@ -0,0 +1,50 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 18 + +def test_sarif_output_for_note(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-error-with-note.c.exe' + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "can't find 'foo.h'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-error-with-note.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 11 + assert phys_loc['region']['endColumn'] == 16 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == '#include <foo.h>\n' + + assert len(results[0]['relatedLocations']) == 1 + note = results[0]['relatedLocations'][0] + phys_loc = note['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-error-with-note.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 11 + assert phys_loc['region']['endColumn'] == 16 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == '#include <foo.h>\n' + assert note['message']['text'] == 'have you looked behind the couch?' diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c new file mode 100644 index 000000000000..f406743f64b4 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c @@ -0,0 +1,76 @@ +/* Example of emitting an error with an associated note. + + Intended output is similar to: + +PATH/test-error-with-note.c:18:11: error: can't find 'foo.h' + 6 | #include <foo.h> + | ^~~~~ +PATH/test-error-with-note.c:18:11: note: have you looked behind the couch? + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122 +123456789012345678901 +#include <foo.h> +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-error-with-note.c.exe", + "test-error-with-note.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 11); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 15); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + /* begin quoted source */ + diagnostic_manager_begin_group (diag_mgr); + + diagnostic *err = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (err, loc_range); + diagnostic_finish (err, "can't find %qs", "foo.h"); + + diagnostic *note = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE); + diagnostic_set_location (note, loc_range); + diagnostic_finish (note, "have you looked behind the couch?"); + + diagnostic_manager_end_group (diag_mgr); + /* end quoted source */ + + return end_test (); +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:11: error: can't find 'foo.h'" } + { dg-begin-multiline-output "" } + 18 | #include <foo.h> + | ^~~~~ + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-error-with-note.c:18:11: note: have you looked behind the couch." } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-error-with-note.c "test-error-with-note-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc new file mode 100644 index 000000000000..e211297c5520 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc @@ -0,0 +1,55 @@ +/* C++ example of emitting an error with an associated note. + + Intended output is similar to: + +PATH/test-error-with-note.c:17:8: error: can't find 'foo' + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ +PATH/test-error-with-note.c:17:8: note: have you looked behind the couch? + + along with the equivalent in SARIF. */ + +#include "libdiagnostics++.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + libdiagnostics::manager mgr; + + auto file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + + auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8); + auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19); + auto loc_range = mgr.new_location_from_range (loc_start, + loc_start, + loc_end); + + libdiagnostics::group g (mgr); + + auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + err.set_location (loc_range); + err.finish ("can't find %qs", "foo"); + + auto note = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE); + note.set_location (loc_range); + note.finish ("have you looked behind the couch?"); + + return 0; +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error-with-note.cc:17:8: error: can't find 'foo'" } + { dg-begin-multiline-output "" } + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-error-with-note.cc:17:8: note: have you looked behind the couch\\\?" } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-error.c b/gcc/testsuite/libdiagnostics.dg/test-error.c new file mode 100644 index 000000000000..6f17ce2c098b --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error.c @@ -0,0 +1,61 @@ +/* Example of emitting an error. + + Intended output is similar to: + +PATH/test-error-with-note.c:6:11: error: can't find 'foo.h' + 6 | #include <foo.h> + | ^~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________1111111 +1234567890123456 +#include <foo.h> +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-error.c.exe", + "test-error.c.sarif", + __FILE__, "c"); + + /* begin quoted source */ + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, main_file, line_num, 11); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, main_file, line_num, 15); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo.h"); + /* end quoted source */ + + return end_test (); +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error.c:17:11: error: can't find 'foo.h'" } + { dg-begin-multiline-output "" } + 17 | #include <foo.h> + | ^~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-error.c "test-error-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-error.cc b/gcc/testsuite/libdiagnostics.dg/test-error.cc new file mode 100644 index 000000000000..6f919e473f4d --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-error.cc @@ -0,0 +1,47 @@ +/* C++ example of emitting an error. + + Intended output is similar to: + +PATH/test-error.cc:16:8: error: can't find 'foo' + 16 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics++.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + libdiagnostics::manager mgr; + + auto file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + + auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8); + auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19); + auto loc_range = mgr.new_location_from_range (loc_start, + loc_start, + loc_end); + + libdiagnostics::diagnostic d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + d.set_location (loc_range); + d.finish ("can't find %qs", "foo"); + + return 0; +}; + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-error.cc:16:8: error: can't find 'foo'" } + { dg-begin-multiline-output "" } + 16 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-example-1.c b/gcc/testsuite/libdiagnostics.dg/test-example-1.c new file mode 100644 index 000000000000..58a146b3f188 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-example-1.c @@ -0,0 +1,43 @@ +/* begin quoted source */ +/* Minimal usage example. */ +#include "libdiagnostics.h" + +static diagnostic_manager *diag_mgr; + +static void +init_diagnostics (void) +{ + diag_mgr = diagnostic_manager_new (); + diagnostic_manager_add_text_sink (diag_mgr, stderr, + DIAGNOSTIC_COLORIZE_IF_TTY); +} + +static void +finish_diagnostics (void) +{ + diagnostic_manager_release (diag_mgr); +} + +static void +do_stuff (void) +{ + const char *username = "Dave"; + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_finish (d, + "I'm sorry %s, I'm afraid I can't do that", + username); +} + +int +main () +{ + init_diagnostics (); + + do_stuff (); + + finish_diagnostics (); +}; +/* end quoted source */ + +/* { dg-regexp "progname: error: I'm sorry Dave, I'm afraid I can't do that" } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py new file mode 100644 index 000000000000..f3dc71c27b56 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint-c.py @@ -0,0 +1,46 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output_with_fixes(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-fix-it-hint.c.exe' + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "unknown field 'colour'; did you mean 'color'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-fix-it-hint.c') + assert phys_loc['region']['startLine'] == 19 + assert phys_loc['region']['startColumn'] == 13 + assert phys_loc['region']['endColumn'] == 19 + assert phys_loc['contextRegion']['startLine'] == 19 + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' return p->colour;\n' + + assert len(results[0]['fixes']) == 1 + fix = results[0]['fixes'][0] + assert len(fix['artifactChanges']) == 1 + change = fix['artifactChanges'][0] + assert change['artifactLocation']['uri'].endswith('test-fix-it-hint.c') + assert len(change['replacements']) == 1 + replacement = change['replacements'][0] + assert replacement['deletedRegion'] == phys_loc['region'] + assert replacement['insertedContent']['text'] == 'color' diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c new file mode 100644 index 000000000000..0d8ee46cb96f --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c @@ -0,0 +1,83 @@ +/* Example of a fix-it hint, including patch generation. + + Intended output is similar to: + +PATH/test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color' + 19 | return p->colour; + | ^~~~~~ + | color + + along with the equivalent in SARIF, and a generated patch (on stderr) to + make the change. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________11111111112 +12345678901234567890 + return p->colour; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-fix-it-hint.c.exe", + "test-fix-it-hint.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_token + = make_range (diag_mgr, main_file, line_num, 13, 18); + + /* begin quoted source */ + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_token); + + diagnostic_add_fix_it_hint_replace (d, loc_token, "color"); + + diagnostic_finish (d, "unknown field %qs; did you mean %qs", "colour", "color"); + /* end quoted source */ + + diagnostic_manager_write_patch (diag_mgr, stderr); + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-fix-it-hint.c:19:13: error: unknown field 'colour'; did you mean 'color'" } + { dg-begin-multiline-output "" } + 19 | return p->colour; + | ^~~~~~ + | color + { dg-end-multiline-output "" } */ + +/* Verify the output from diagnostic_manager_write_patch. + We expect the patch to begin with a header, containing this + source filename, via an absolute path. + Given the path, we can only capture it via regexps. */ +/* { dg-regexp "\\-\\-\\- .*" } */ +/* { dg-regexp "\\+\\+\\+ .*" } */ +/* Use #if 0/#endif rather than comments, to allow the text to contain + a comment. */ +#if 0 +{ dg-begin-multiline-output "" } +@@ -16,7 +16,7 @@ + /* + _________11111111112 + 12345678901234567890 +- return p->colour; ++ return p->color; + */ + const int line_num = __LINE__ - 2; + +{ dg-end-multiline-output "" } +#endif + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-fix-it-hint.c "test-fix-it-hint-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc new file mode 100644 index 000000000000..92c7f07117bd --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc @@ -0,0 +1,74 @@ +/* C++ example of a fix-it hint, including patch generation. + + Intended output is similar to: + +PATH/test-fix-it-hint.cc:19:13: error: unknown field 'colour'; did you mean 'color' + 19 | return p->colour; + | ^~~~~~ + | color + + along with the equivalent in SARIF, and a generated patch (on stderr) to + make the change. */ + +#include "libdiagnostics++.h" +#include "test-helpers++.h" + +/* +_________11111111112 +12345678901234567890 + return p->colour; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + libdiagnostics::manager mgr; + + auto file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + + auto loc_token = make_range (mgr, file, line_num, 13, 18); + + auto d = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR); + d.set_location (loc_token); + + d.add_fix_it_hint_replace (loc_token, "color"); + + d.finish ("unknown field %qs; did you mean %qs", "colour", "color"); + + mgr.write_patch (stderr); + + return 0; +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-fix-it-hint.cc:19:13: error: unknown field 'colour'; did you mean 'color'" } + { dg-begin-multiline-output "" } + 19 | return p->colour; + | ^~~~~~ + | color + { dg-end-multiline-output "" } */ + +/* Verify the output from diagnostic_manager_write_patch. + We expect the patch to begin with a header, containing this + source filename, via an absolute path. + Given the path, we can only capture it via regexps. */ +/* { dg-regexp "\\-\\-\\- .*" } */ +/* { dg-regexp "\\+\\+\\+ .*" } */ +/* Use #if 0/#endif rather than comments, to allow the text to contain + a comment. */ +#if 0 +{ dg-begin-multiline-output "" } +@@ -16,7 +16,7 @@ + /* + _________11111111112 + 12345678901234567890 +- return p->colour; ++ return p->color; + */ + const int line_num = __LINE__ - 2; + +{ dg-end-multiline-output "" } +#endif diff --git a/gcc/testsuite/libdiagnostics.dg/test-helpers++.h b/gcc/testsuite/libdiagnostics.dg/test-helpers++.h new file mode 100644 index 000000000000..c8ff2def1ffa --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-helpers++.h @@ -0,0 +1,28 @@ +/* Common utility code shared between test cases. */ + +#ifndef TEST_HELPERSPP_H +#define TEST_HELPERSPP_H + +namespace libdiagnostics { + +inline physical_location +make_range (manager &mgr, + file f, + line_num_t line_num, + column_num_t start_column, + column_num_t end_column) +{ + auto loc_start = mgr.new_location_from_file_line_column (f, + line_num, + start_column); + auto loc_end = mgr.new_location_from_file_line_column (f, + line_num, + end_column); + return mgr.new_location_from_range (loc_start, + loc_start, + loc_end); +} + +} // namespace libdiagnostics + +#endif /* #ifndef TEST_HELPERSPP_H */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-helpers.h b/gcc/testsuite/libdiagnostics.dg/test-helpers.h new file mode 100644 index 000000000000..a578850e9d50 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-helpers.h @@ -0,0 +1,72 @@ +/* Common utility code shared between test cases. */ + +#ifndef TEST_HELPERS_H +#define TEST_HELPERS_H + +const diagnostic_physical_location * +make_range (diagnostic_manager *diag_mgr, + const diagnostic_file *file, + diagnostic_line_num_t line_num, + diagnostic_column_num_t start_column, + diagnostic_column_num_t end_column) +{ + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + file, + line_num, + start_column); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + file, + line_num, + end_column); + return diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); +} + +/* A begin_test/end_test pair to consolidate the code shared by tests: + create a diagnostic_manager, a main file, a text sink, and a SARIF sink, + and clean these up after emitting zero or more diagnostics. */ + +static diagnostic_manager *diag_mgr; +static const diagnostic_file *main_file; +static FILE *sarif_outfile; + +static void +begin_test (const char *tool_name, + const char *sarif_output_name, + const char *main_file_name, + const char *source_language) +{ + diag_mgr = diagnostic_manager_new (); + + /* We need to set this for generated .sarif files to validate + against the schema. */ + diagnostic_manager_set_tool_name (diag_mgr, tool_name); + + main_file = diagnostic_manager_new_file (diag_mgr, + main_file_name, + source_language); + + diagnostic_manager_add_text_sink (diag_mgr, stderr, + DIAGNOSTIC_COLORIZE_IF_TTY); + sarif_outfile = fopen (sarif_output_name, "w"); + if (sarif_outfile) + diagnostic_manager_add_sarif_sink (diag_mgr, + sarif_outfile, + main_file, + DIAGNOSTIC_SARIF_VERSION_2_1_0); +} + +static int +end_test (void) +{ + diagnostic_manager_release (diag_mgr); + if (sarif_outfile) + fclose (sarif_outfile); + return 0; +} + +#endif /* #ifndef TEST_HELPERS_H */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c new file mode 100644 index 000000000000..39978b22f232 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c @@ -0,0 +1,71 @@ +/* Example of multiple locations, with labelling of ranges. + + Intended output is similar to: + +PATH/test-labelled-ranges.c:9:6: error: mismatching types: 'int' and 'const char *' + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________11111111112 +12345678901234567890 + 42 + "foo" +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-labelled-ranges.c.exe", + "test-labelled-ranges.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_operator + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 6); + + /* begin quoted source */ + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_operator); + diagnostic_add_location_with_label (d, + make_range (diag_mgr, + main_file, + line_num, 3, 4), + "int"); + diagnostic_add_location_with_label (d, + make_range (diag_mgr, + main_file, + line_num, 8, 12), + "const char *"); + + diagnostic_finish (d, "mismatching types: %qs and %qs", "int", "const char *"); + /* end quoted source */ + + return end_test (); +} + +/* Check the output from the text sink. */ +/* { dg-regexp "\[^\n\r\]+test-labelled-ranges.c:19:6: error: mismatching types: 'int' and 'const char \\*'" } */ +/* { dg-begin-multiline-output "" } + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-labelled-ranges.c "test-labelled-ranges.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc new file mode 100644 index 000000000000..1c1c050e3042 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc @@ -0,0 +1,64 @@ +/* C++ example of multiple locations, with labelling of ranges. + + Intended output is similar to: + +PATH/test-labelled-ranges.cc:19:6: error: mismatching types: 'int' and 'const char *' + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + + along with the equivalent in SARIF. */ + +#include "libdiagnostics++.h" +#include "test-helpers++.h" + +/* +_________11111111112 +12345678901234567890 + 42 + "foo" +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + FILE *sarif_outfile; + libdiagnostics::manager mgr; + mgr.set_tool_name ("test-labelled-ranges.cc.exe"); + + libdiagnostics::file file = mgr.new_file (__FILE__, "c"); + + mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY); + sarif_outfile = fopen ("test-labelled-ranges.cc.sarif", "w"); + if (sarif_outfile) + mgr.add_sarif_sink (sarif_outfile, file, DIAGNOSTIC_SARIF_VERSION_2_1_0); + + auto loc_operator = mgr.new_location_from_file_line_column (file, line_num, 6); + + auto d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR)); + d.set_location (loc_operator); + d.add_location_with_label (make_range (mgr, file, line_num, 3, 4), + "int"); + d.add_location_with_label (make_range (mgr, file, line_num, 8, 12), + "const char *"); + d.finish ("mismatching types: %qs and %qs", "int", "const char *"); + + return 0; +} + +/* Check the output from the text sink. */ +/* { dg-regexp "\[^\n\r\]+test-labelled-ranges.cc:19:6: error: mismatching types: 'int' and 'const char \\*'" } */ +/* { dg-begin-multiline-output "" } + 19 | 42 + "foo" + | ~~ ^ ~~~~~ + | | | + | int const char * + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-labelled-ranges.cc "test-labelled-ranges.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py new file mode 100644 index 000000000000..dce404f92fd6 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.py @@ -0,0 +1,48 @@ +# Verify the SARIF output of test-labelled-ranges.{c,cc} + +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] \ + == "mismatching types: 'int' and 'const char *'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['region']['startLine'] == 19 + assert phys_loc['region']['startColumn'] == 6 + assert phys_loc['region']['endColumn'] == 7 + assert phys_loc['contextRegion']['startLine'] == 19 + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' 42 + "foo"\n' + + annotations = location['annotations'] + assert len(annotations) == 2 + + assert annotations[0]['startLine'] == 19 + assert annotations[0]['startColumn'] == 3 + assert annotations[0]['endColumn'] == 5 + assert annotations[0]['message']['text'] == 'int' + + assert annotations[1]['startLine'] == 19 + assert annotations[1]['startColumn'] == 8 + assert annotations[1]['endColumn'] == 13 + assert annotations[1]['message']['text'] == 'const char *' diff --git a/gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py b/gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py new file mode 100644 index 000000000000..7448a1e0dac9 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-logical-location-c.py @@ -0,0 +1,37 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output_with_logical_location(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-logical-location.c.exe' + + results = run['results'] + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == 'error' + assert result['level'] == 'error' + assert result['message']['text'] == "can't find 'foo'" + assert len(result['locations']) == 1 + location = result['locations'][0] + + assert len(location['logicalLocations']) == 1 + logical_loc = location['logicalLocations'][0] + assert logical_loc['name'] == 'test_short_name' + assert logical_loc['fullyQualifiedName'] == 'test_qualified_name' + assert logical_loc['decoratedName'] == 'test_decorated_name' + assert logical_loc['kind'] == 'function' diff --git a/gcc/testsuite/libdiagnostics.dg/test-logical-location.c b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c new file mode 100644 index 000000000000..7d0bed9600e9 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c @@ -0,0 +1,81 @@ +/* Example of using a logical location. + + Intended output is similar to: + +In function 'test_qualified_name': +PATH/test-error-with-note.c:18:8: error: can't find 'foo' + 18 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source: +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-logical-location.c.exe", + "test-logical-location.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + /* begin quoted source */ + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + const diagnostic_logical_location *logical_loc + = diagnostic_manager_new_logical_location (diag_mgr, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION, + NULL, /* parent */ + "test_short_name", + "test_qualified_name", + "test_decorated_name"); + + diagnostic_set_logical_location (d, logical_loc); + + diagnostic_finish (d, "can't find %qs", "foo"); + /* end quoted source */ + + return end_test (); +} + +/* Check the output from the text sink. */ +/* { dg-begin-multiline-output "" } +In function 'test_qualified_name': + { dg-end-multiline-output "" } */ +/* { dg-regexp "\[^\n\r\]+test-logical-location.c:18:8: error: can't find 'foo'" } */ +/* { dg-begin-multiline-output "" } + 18 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-logical-location.c "test-logical-location-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-metadata-c.py b/gcc/testsuite/libdiagnostics.dg/test-metadata-c.py new file mode 100644 index 000000000000..fc83658e0c58 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-metadata-c.py @@ -0,0 +1,45 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output_metadata(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'FooChecker' + assert tool['driver']['fullName'] == 'FooChecker 0.1 (en_US)' + assert tool['driver']['version'] == '0.1' + assert tool['driver']['informationUri'] == 'https://www.example.com/0.1/' + + taxonomies = run["taxonomies"] + assert len(taxonomies) == 1 + + cwe = taxonomies[0] + assert cwe['name'] == 'CWE' + assert cwe['version'] == '4.7' + assert cwe['organization'] == 'MITRE' + assert cwe['shortDescription']['text'] \ + == 'The MITRE Common Weakness Enumeration' + assert len(cwe['taxa']) == 1 + assert cwe['taxa'][0]['id'] == '242' + assert cwe['taxa'][0]['helpUri'] \ + == 'https://cwe.mitre.org/data/definitions/242.html' + + results = run['results'] + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == 'warning' + assert result['level'] == 'warning' + assert result['message']['text'] == "never use 'gets'" diff --git a/gcc/testsuite/libdiagnostics.dg/test-metadata.c b/gcc/testsuite/libdiagnostics.dg/test-metadata.c new file mode 100644 index 000000000000..7881c9eca3e8 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-metadata.c @@ -0,0 +1,61 @@ +/* Example of setting a CWE and adding extra metadata. + + Intended output is similar to: + +PATH/test-metadata.c:21:3: warning: never use 'gets' [CWE-242] [STR34-C] + 21 | gets (buf); + | ^~~~~~~~~~ + + where the metadata tags are linkified in a sufficiently capable terminal, + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source: +_________11111111112 +12345678901234567890 +void test_cwe (void) +{ + char buf[1024]; + gets (buf); +} +*/ +const int line_num = __LINE__ - 3; + +int +main () +{ + begin_test ("FooChecker", + "test-metadata.c.sarif", + __FILE__, "c"); + + diagnostic_manager_set_full_name (diag_mgr, "FooChecker 0.1 (en_US)"); + diagnostic_manager_set_version_string (diag_mgr, "0.1"); + diagnostic_manager_set_version_url (diag_mgr, "https://www.example.com/0.1/"); + + const diagnostic_physical_location *loc_token + = make_range (diag_mgr, main_file, line_num, 3, 12); + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_token); + diagnostic_set_cwe (d, 242); /* CWE-242: Use of Inherently Dangerous Function. */ + diagnostic_add_rule (d, "STR34-C", "https://example.com/"); + + diagnostic_finish (d, "never use %qs", "gets"); + + return end_test (); +} + +/* { dg-regexp "\[^\n\r\]+test-metadata.c:21:3: warning: never use 'gets' \\\[CWE-242\\\] \\\[STR34-C\\\]" } */ +/* { dg-begin-multiline-output "" } + 21 | gets (buf); + | ^~~~~~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-metadata.c "test-metadata-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py new file mode 100644 index 000000000000..3189fcf6d311 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines-c.py @@ -0,0 +1,83 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-multiple-lines.c.exe' + + results = run['results'] + assert len(results) == 1 + result = results[0] + assert result['ruleId'] == 'warning' + assert result['level'] == 'warning' + assert result['message']['text'] == "missing comma" + assert len(result['locations']) == 1 + + # The primary location should be that of the missing comma + location = result['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-multiple-lines.c') + assert phys_loc['region']['startLine'] == 23 + assert phys_loc['region']['startColumn'] == 29 + assert phys_loc['region']['endColumn'] == 30 + assert phys_loc['contextRegion']['startLine'] == 23 + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' "bar"\n' + + assert len(location['relationships']) == 3 + location['relationships'][0]['target'] == 0 + location['relationships'][0]['kinds'] == ['relevant'] + location['relationships'][1]['target'] == 1 + location['relationships'][1]['kinds'] == ['relevant'] + location['relationships'][2]['target'] == 2 + location['relationships'][2]['kinds'] == ['relevant'] + + # We should be capturing the secondary locations in relatedLocations + assert len(result['relatedLocations']) == 3 + + rel_loc_0 = result['relatedLocations'][0] + assert get_location_artifact_uri(rel_loc_0) \ + .endswith('test-multiple-lines.c') + assert get_location_snippet_text(rel_loc_0) \ + == 'const char *strs[3] = {"foo",\n' + assert get_location_physical_region(rel_loc_0)['startLine'] == 22 + assert get_location_physical_region(rel_loc_0)['startColumn'] == 24 + assert get_location_physical_region(rel_loc_0)['endColumn'] == 29 + assert rel_loc_0['id'] == 0 + assert 'relationships' not in rel_loc_0 + + rel_loc_1 = result['relatedLocations'][1] + assert get_location_artifact_uri(rel_loc_1) \ + .endswith('test-multiple-lines.c') + assert get_location_snippet_text(rel_loc_1) \ + == ' "bar"\n' + assert get_location_physical_region(rel_loc_1)['startLine'] == 23 + assert get_location_physical_region(rel_loc_1)['startColumn'] == 24 + assert get_location_physical_region(rel_loc_1)['endColumn'] == 29 + assert rel_loc_1['id'] == 1 + assert 'relationships' not in rel_loc_1 + + rel_loc_2 = result['relatedLocations'][2] + assert get_location_artifact_uri(rel_loc_2) \ + .endswith('test-multiple-lines.c') + assert get_location_snippet_text(rel_loc_2) \ + == ' "baz"};\n' + assert get_location_physical_region(rel_loc_2)['startLine'] == 24 + assert get_location_physical_region(rel_loc_2)['startColumn'] == 24 + assert get_location_physical_region(rel_loc_2)['endColumn'] == 29 + assert rel_loc_2['id'] == 2 + assert 'relationships' not in rel_loc_2 diff --git a/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c new file mode 100644 index 000000000000..888897ae7919 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c @@ -0,0 +1,78 @@ +/* Example of a warning with multiple locations in various source lines, + with an insertion fix-it hint. + + Intended output is similar to: + +/PATH/test-multiple-lines.c:23:29: warning: missing comma + 22 | const char *strs[3] = {"foo", + | ~~~~~ + 23 | "bar" + | ~~~~~^ + 24 | "baz"}; + | ~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source (missing comma after "bar"): +_________11111111112222222222 +12345678901234567890123456789 +const char *strs[3] = {"foo", + "bar" + "baz"}; +*/ +const int foo_line_num = __LINE__ - 4; + +int +main () +{ + begin_test ("test-multiple-lines.c.exe", + "test-multiple-lines.c.sarif", + __FILE__, "c"); + + /* begin quoted source */ + const diagnostic_physical_location *loc_comma + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + foo_line_num + 1, + 29); + const diagnostic_physical_location *loc_foo + = make_range (diag_mgr, main_file, foo_line_num, 24, 28); + const diagnostic_physical_location *loc_bar + = make_range (diag_mgr, main_file, foo_line_num + 1, 24, 28); + const diagnostic_physical_location *loc_baz + = make_range (diag_mgr, main_file, foo_line_num + 2, 24, 28); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_comma); + diagnostic_add_location (d, loc_foo); + diagnostic_add_location (d, loc_bar); + diagnostic_add_location (d, loc_baz); + + diagnostic_add_fix_it_hint_insert_after (d, loc_bar, ","); + + diagnostic_finish (d, "missing comma"); + /* end quoted source */ + + return end_test (); +}; + +/* { dg-regexp "\[^\n\r\]+test-multiple-lines.c:23:29: warning: missing comma" } */ +/* { dg-begin-multiline-output "" } + 22 | const char *strs[3] = {"foo", + | ~~~~~ + 23 | "bar" + | ~~~~~^ + 24 | "baz"}; + | ~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-multiple-lines.c "test-multiple-lines-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-column-c.py b/gcc/testsuite/libdiagnostics.dg/test-no-column-c.py new file mode 100644 index 000000000000..afef98493141 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-column-c.py @@ -0,0 +1,35 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 16 + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-no-column.c.exe' + + results = run['results'] + assert len(results) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-no-column.c') + assert phys_loc['region']['startLine'] == expected_line_num + # We should have no column properties: + assert 'startColumn' not in phys_loc['region'] + assert 'endColumn' not in phys_loc['region'] + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == '#include <foo.h>\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-column.c b/gcc/testsuite/libdiagnostics.dg/test-no-column.c new file mode 100644 index 000000000000..5354f7543f17 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-column.c @@ -0,0 +1,54 @@ +/* Example of emitting an error without a column number. + + Intended output is similar to: + +PATH/test-error-with-note.c:6: error: can't find 'foo' + 6 | #include <foo.h> + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122 +123456789012345678901 +#include <foo.h> +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-no-column.c.exe", + "test-no-column.c.sarif", + __FILE__, "c"); + + /* begin quoted source */ + const diagnostic_physical_location *loc + = diagnostic_manager_new_location_from_file_and_line (diag_mgr, + main_file, + line_num); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc); + + diagnostic_finish (d, "can't find %qs", "foo.h"); + /* end quoted source */ + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-no-column.c:16: error: can't find 'foo.h'" } + { dg-begin-multiline-output "" } + 16 | #include <foo.h> + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-no-column.c "test-no-column-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py new file mode 100644 index 000000000000..9ce1c2a0e3a6 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics-c.py @@ -0,0 +1,42 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_file_name = 'test-no-diagnostics.c' + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == True + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'].endswith(expected_file_name) + assert artifacts[0]['sourceLanguage'] == 'c' + # We don't bother capturing the contents if there are + # no diagnostics to display + assert 'contents' not in artifacts[0] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 0 diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c new file mode 100644 index 000000000000..78e186ab95ad --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-no-diagnostics.c @@ -0,0 +1,25 @@ +/* Test of the "no diagnostics are emitted" case. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +int +main () +{ + begin_test ("test-no-diagnostics.c.exe", + "test-no-diagnostics.c.sarif", + __FILE__, "c"); + + /* No-op. */ + + return end_test (); +}; + +/* There should be no output from the text sink. */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-no-diagnostics.c "test-no-diagnostics-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py new file mode 100644 index 000000000000..cd4e6e214498 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint-c.py @@ -0,0 +1,54 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 21 + +def test_sarif_output_with_fixes(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-note-with-fix-it-hint.c.exe' + + results = run['results'] + assert len(results) == 1 + result = results[0] + assert result['ruleId'] == 'error' + assert result['level'] == 'error' + assert result['message']['text'] == "unknown field 'colour'" + assert len(result['locations']) == 1 + location = result['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-note-with-fix-it-hint.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 13 + assert phys_loc['region']['endColumn'] == 19 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' return p->colour;\n' + + assert len(result['relatedLocations']) == 1 + note = result['relatedLocations'][0] + phys_loc = note['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith('test-note-with-fix-it-hint.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 13 + assert phys_loc['region']['endColumn'] == 19 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' return p->colour;\n' + assert note['message']['text'] == "did you mean 'color'" + + # TODO: we don't yet capture fix-it hints on notes (PR other/116164) + assert 'fixes' not in result diff --git a/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c new file mode 100644 index 000000000000..19fa7c1b46d6 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c @@ -0,0 +1,69 @@ +/* Example of a grouped error and note, with a fix-it hint on the note. + + Intended output is similar to: + +/PATH/test-note-with-fix-it-hint.c:21:13: error: unknown field 'colour' + 21 | return p->colour; + | ^~~~~~ +/PATH/test-note-with-fix-it-hint.c:21:13: note: did you mean 'color' + 21 | return p->colour; + | ^~~~~~ + | color + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* Placeholder source: +_________11111111112 +12345678901234567890 + return p->colour; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-note-with-fix-it-hint.c.exe", + "test-note-with-fix-it-hint.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_token + = make_range (diag_mgr, main_file, line_num, 13, 18); + + diagnostic_manager_begin_group (diag_mgr); + + diagnostic *err = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (err, loc_token); + diagnostic_finish (err, "unknown field %qs", "colour"); + + diagnostic *n = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE); + diagnostic_set_location (n, loc_token); + diagnostic_add_fix_it_hint_replace (n, loc_token, "color"); + diagnostic_finish (n, "did you mean %qs", "color"); + + diagnostic_manager_end_group (diag_mgr); + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-note-with-fix-it-hint.c:21:13: error: unknown field 'colour'" } + { dg-begin-multiline-output "" } + 21 | return p->colour; + | ^~~~~~ + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-note-with-fix-it-hint.c:21:13: note: did you mean 'color'" } + { dg-begin-multiline-output "" } + 21 | return p->colour; + | ^~~~~~ + | color + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-note-with-fix-it-hint.c "test-note-with-fix-it-hint-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c b/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c new file mode 100644 index 000000000000..c1468553a122 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c @@ -0,0 +1,59 @@ +/* Example of controlling options for text sinks, + with multiple text sinks, + and color output. */ + +#include "libdiagnostics.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + + diagnostic_text_sink *sink_1 + = diagnostic_manager_add_text_sink (diag_mgr, stderr, + DIAGNOSTIC_COLORIZE_NO); + diagnostic_text_sink_set_source_printing_enabled (sink_1, 0); + + diagnostic_text_sink *sink_2 + = diagnostic_manager_add_text_sink (diag_mgr, stderr, + DIAGNOSTIC_COLORIZE_YES); + diagnostic_text_sink_set_labelled_source_colorization_enabled (sink_2, 0); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c"); + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo"); + + diagnostic_manager_release (diag_mgr); + return 0; +}; + +/* Verify the output from text sink 1. */ +/* { dg-regexp "\[^\n\r\]+test-text-sink-options.c:10:8: error: can't find 'foo'" } */ + +/* Verify the output from text sink 2. + { dg-regexp "\[^\n\r\]+test-text-sink-options.c:10:8:" } + { dg-begin-multiline-output "" } +[m[K [01;31m[Kerror: [m[Kcan't find '[01m[Kfoo[m[K' + 10 | PRINT "hello world!"; + | [01;31m[K^~~~~~~~~~~~[m[K + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning-c.py b/gcc/testsuite/libdiagnostics.dg/test-warning-c.py new file mode 100644 index 000000000000..c6e3752c4f41 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning-c.py @@ -0,0 +1,54 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 17 +expected_file_name = 'test-warning.c' + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == True + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'].endswith(expected_file_name) + assert artifacts[0]['sourceLanguage'] == 'c' + assert 'PRINT' in artifacts[0]['contents']['text'] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'warning' + assert results[0]['level'] == 'warning' + assert results[0]['message']['text'] == "this is a warning" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name) + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 8 + assert phys_loc['region']['endColumn'] == 20 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py new file mode 100644 index 000000000000..5d3bbc47e171 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path-c.py @@ -0,0 +1,108 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +final_line_num = 34 + +line_num_call_to_PyList_New = final_line_num - 7; +line_num_for_loop = final_line_num - 5; +line_num_call_to_PyList_Append = final_line_num - 3; + +expected_file_name = 'test-warning-with-path.c' + +def test_sarif_output_for_warning_with_path(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == expected_file_name + '.exe' + + results = run['results'] + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == 'warning' + assert result['level'] == 'warning' + assert result['message']['text'] \ + == "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" + assert len(result['locations']) == 1 + location = result['locations'][0] + + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'].endswith(expected_file_name) + assert phys_loc['region']['startLine'] \ + == line_num_call_to_PyList_Append + assert phys_loc['region']['startColumn'] == 5 + assert phys_loc['region']['endColumn'] == 30 + assert phys_loc['contextRegion']['startLine'] \ + == line_num_call_to_PyList_Append + assert phys_loc['contextRegion']['snippet']['text'] \ + == ' PyList_Append(list, item);\n' + + assert len(location['logicalLocations']) == 1 + logical_loc = location['logicalLocations'][0] + assert logical_loc['name'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['fullyQualifiedName'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['decoratedName'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['kind'] == 'function' + + assert len(result['codeFlows']) == 1 + assert len(result['codeFlows'][0]['threadFlows']) == 1 + thread_flow = result['codeFlows'][0]['threadFlows'][0] + + assert len(thread_flow['locations']) == 3 + + tfl_0 = thread_flow['locations'][0] + tfl_0_loc = tfl_0['location'] + assert get_location_artifact_uri(tfl_0_loc).endswith(expected_file_name) + assert get_location_physical_region(tfl_0_loc)['startLine'] \ + == line_num_call_to_PyList_New + assert get_location_physical_region(tfl_0_loc)['startColumn'] == 10 + assert get_location_physical_region(tfl_0_loc)['endColumn'] == 23 + assert get_location_snippet_text(tfl_0_loc) \ + == ' list = PyList_New(0);\n' + assert tfl_0_loc['logicalLocations'] == location['logicalLocations'] + assert tfl_0_loc['message']['text'] \ + == "when 'PyList_New' fails, returning NULL" + assert tfl_0['nestingLevel'] == 0 + assert tfl_0['executionOrder'] == 1 + + tfl_1 = thread_flow['locations'][1] + tfl_1_loc = tfl_1['location'] + assert get_location_artifact_uri(tfl_1_loc).endswith(expected_file_name) + assert get_location_physical_region(tfl_1_loc)['startLine'] \ + == line_num_for_loop + assert get_location_physical_region(tfl_1_loc)['startColumn'] == 15 + assert get_location_physical_region(tfl_1_loc)['endColumn'] == 24 + assert get_location_snippet_text(tfl_1_loc) \ + == ' for (i = 0; i < count; i++) {\n' + assert tfl_1_loc['logicalLocations'] == location['logicalLocations'] + assert tfl_1_loc['message']['text'] \ + == "when 'i < count'" + assert tfl_1['nestingLevel'] == 0 + assert tfl_1['executionOrder'] == 2 + + tfl_2 = thread_flow['locations'][2] + tfl_2_loc = tfl_2['location'] + assert get_location_artifact_uri(tfl_2_loc).endswith(expected_file_name) + assert get_location_physical_region(tfl_2_loc)['startLine'] \ + == line_num_call_to_PyList_Append + assert get_location_physical_region(tfl_2_loc)['startColumn'] == 5 + assert get_location_physical_region(tfl_2_loc)['endColumn'] == 30 + assert get_location_snippet_text(tfl_2_loc) \ + == ' PyList_Append(list, item);\n' + assert tfl_2_loc['logicalLocations'] == location['logicalLocations'] + assert tfl_2_loc['message']['text'] \ + == "when calling 'PyList_Append', passing NULL from (1) as argument 1" + assert tfl_2['nestingLevel'] == 0 + assert tfl_2['executionOrder'] == 3 diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c new file mode 100644 index 000000000000..4ff5548b1a30 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning-with-path.c @@ -0,0 +1,138 @@ +/* Example of emitting a warning with an execution path. + +TODO: + + Intended output is similar to: + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122222222223333333333444444444455555555556 +123456789012345678901234567890123456789012345678901234567890 +begin fake source +PyObject * +make_a_list_of_random_ints_badly(PyObject *self, + PyObject *args) +{ + PyObject *list, *item; + long count, i; + + if (!PyArg_ParseTuple(args, "i", &count)) { + return NULL; + } + + list = PyList_New(0); + + for (i = 0; i < count; i++) { + item = PyLong_FromLong(random()); + PyList_Append(list, item); + } + + return list; +} +end fake source +*/ +const int final_line_num = __LINE__ - 4; /* line of "return list;" */ + +/* begin line consts */ +const int line_num_call_to_PyList_New = final_line_num - 7; +const int line_num_for_loop = final_line_num - 5; +const int line_num_call_to_PyList_Append = final_line_num - 3; +/* end line consts */ + +int +main () +{ + begin_test ("test-warning-with-path.c.exe", + "test-warning-with-path.c.sarif", + __FILE__, "c"); + + /* begin full example */ + /* begin create phys locs */ + const diagnostic_physical_location *loc_call_to_PyList_New + = make_range (diag_mgr, main_file, line_num_call_to_PyList_New, 10, 22); + const diagnostic_physical_location *loc_for_cond + = make_range (diag_mgr, main_file, line_num_for_loop, 15, 23); + const diagnostic_physical_location *loc_call_to_PyList_Append + = make_range (diag_mgr, main_file, line_num_call_to_PyList_Append, 5, 29); + /* end create phys locs */ + + /* begin create logical locs */ + const char *funcname = "make_a_list_of_random_ints_badly"; + const diagnostic_logical_location *logical_loc + = diagnostic_manager_new_logical_location (diag_mgr, + DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION, + NULL, /* parent */ + funcname, + funcname, + funcname); + /* end create logical locs */ + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_call_to_PyList_Append); + diagnostic_set_logical_location (d, logical_loc); + + /* begin path creation */ + diagnostic_execution_path *path = diagnostic_add_execution_path (d); + + diagnostic_event_id alloc_event_id + = diagnostic_execution_path_add_event (path, + loc_call_to_PyList_New, + logical_loc, 0, + "when %qs fails, returning NULL", + "PyList_New"); + diagnostic_execution_path_add_event (path, + loc_for_cond, + logical_loc, 0, + "when %qs", "i < count"); + diagnostic_execution_path_add_event (path, + loc_call_to_PyList_Append, + logical_loc, 0, + "when calling %qs, passing NULL from %@ as argument %i", + "PyList_Append", &alloc_event_id, 1); + /* end path creation */ + + diagnostic_finish (d, + "passing NULL as argument %i to %qs" + " which requires a non-NULL parameter", + 1, "PyList_Append"); + /* end full example */ + + return end_test (); +}; + +/* Check the output from the text sink. + { dg-begin-multiline-output "" } +In function 'make_a_list_of_random_ints_badly': + { dg-end-multiline-output "" } + { dg-regexp "\[^\n\r\]+test-warning-with-path.c:31:5: warning: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" } + { dg-begin-multiline-output "" } + 31 | PyList_Append(list, item); + | ^~~~~~~~~~~~~~~~~~~~~~~~~ + 'make_a_list_of_random_ints_badly': events 1-3 + 27 | list = PyList_New(0); + | ^~~~~~~~~~~~~ + | | + | (1) when 'PyList_New' fails, returning NULL + 28 | + 29 | for (i = 0; i < count; i++) { + | ~~~~~~~~~ + | | + | (2) when 'i < count' + 30 | item = PyLong_FromLong(random()); + 31 | PyList_Append(list, item); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + | | + | (3) when calling 'PyList_Append', passing NULL from (1) as argument 1 + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-warning-with-path.c "test-warning-with-path-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning.c b/gcc/testsuite/libdiagnostics.dg/test-warning.c new file mode 100644 index 000000000000..252f646f2f09 --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-warning.c @@ -0,0 +1,67 @@ +/* Example of emitting a warning. + + Intended output is similar to: + +/PATH/test-warning.c:17:8: warning: this is a warning + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + + along with the equivalent in SARIF. */ + +#include "libdiagnostics.h" +#include "test-helpers.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + begin_test ("test-warning.c.exe", + "test-warning.c.sarif", + __FILE__, "c"); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, + main_file, + line_num, + 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + /* begin quoted source */ + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_WARNING); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "this is a warning"); + /* end quoted source */ + + return end_test (); +} + +/* Verify the output from the text sink. + { dg-regexp "\[^\n\r\]+test-warning.c:17:8: warning: this is a warning" } + { dg-begin-multiline-output "" } + 17 | PRINT "hello world!"; + | ^~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-warning.c "test-warning-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py new file mode 100644 index 000000000000..2d5ebe93b1fc --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file-c.py @@ -0,0 +1,55 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_line_num = 8 + +def test_sarif_output(sarif): + schema = sarif['$schema'] + assert schema == 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json' + + version = sarif['version'] + assert version == '2.1.0' + + runs = sarif['runs'] + run = runs[0] + + tool = run['tool'] + assert tool['driver']['name'] == 'test-write-sarif-to-file.c.exe' + + invocations = run['invocations'] + assert len(invocations) == 1 + assert 'workingDirectory' in invocations[0] + assert 'startTimeUtc' in invocations[0] + assert invocations[0]['executionSuccessful'] == False + assert invocations[0]['toolExecutionNotifications'] == [] + assert 'endTimeUtc' in invocations[0] + + artifacts = run['artifacts'] + assert len(artifacts) == 1 + assert artifacts[0]['location']['uri'] \ + .endswith('test-write-sarif-to-file.c') + assert artifacts[0]['sourceLanguage'] == 'c' + assert 'PRINT' in artifacts[0]['contents']['text'] + assert artifacts[0]['roles'] == ["analysisTarget"] + + results = run['results'] + assert len(results) == 1 + assert results[0]['ruleId'] == 'error' + assert results[0]['level'] == 'error' + assert results[0]['message']['text'] == "can't find 'foo'" + assert len(results[0]['locations']) == 1 + location = results[0]['locations'][0] + phys_loc = location['physicalLocation'] + assert phys_loc['artifactLocation']['uri'] \ + .endswith('test-write-sarif-to-file.c') + assert phys_loc['region']['startLine'] == expected_line_num + assert phys_loc['region']['startColumn'] == 8 + assert phys_loc['region']['endColumn'] == 20 + assert phys_loc['contextRegion']['startLine'] == expected_line_num + assert phys_loc['contextRegion']['snippet']['text'] \ + == 'PRINT "hello world!";\n' diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c new file mode 100644 index 000000000000..637935e4598b --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c @@ -0,0 +1,55 @@ +/* Example of writing diagnostics as SARIF to a file. */ + +#include "libdiagnostics.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + FILE *sarif_outfile = fopen ("test-write-sarif-to-file.c.sarif", "w"); + if (!sarif_outfile) + return -1; + + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + diagnostic_manager_set_tool_name (diag_mgr, "test-write-sarif-to-file.c.exe"); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c"); + + diagnostic_manager_add_sarif_sink (diag_mgr, sarif_outfile, file, + DIAGNOSTIC_SARIF_VERSION_2_1_0); + + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo"); + + diagnostic_manager_release (diag_mgr); + + fclose (sarif_outfile); + + return 0; +}; + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-write-sarif-to-file.c "test-write-sarif-to-file-c.py" } } */ diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c new file mode 100644 index 000000000000..8ad448c8c9ce --- /dev/null +++ b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c @@ -0,0 +1,47 @@ +/* Example of writing diagnostics in text form, but to a file, + rather than stderr. */ + +#include "libdiagnostics.h" + +/* +_________111111111122 +123456789012345678901 +PRINT "hello world!"; +*/ +const int line_num = __LINE__ - 2; + +int +main () +{ + FILE *outfile = fopen ("test.txt", "w"); + if (!outfile) + return -1; + + diagnostic_manager *diag_mgr = diagnostic_manager_new (); + + diagnostic_manager_add_text_sink (diag_mgr, outfile, + DIAGNOSTIC_COLORIZE_NO); + + const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c"); + const diagnostic_physical_location *loc_start + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8); + const diagnostic_physical_location *loc_end + = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19); + const diagnostic_physical_location *loc_range + = diagnostic_manager_new_location_from_range (diag_mgr, + loc_start, + loc_start, + loc_end); + + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + diagnostic_set_location (d, loc_range); + + diagnostic_finish (d, "can't find %qs", "foo"); + + diagnostic_manager_release (diag_mgr); + + fclose (outfile); + + return 0; +}; -- 2.26.3