From 414e40efd04f55568c0fccd8465b1cc7f95fecf1 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 10 Jul 2018 13:30:11 -0400
Subject: [PATCH 19/51] FIXME: WIP on proper test coverage for JSON output

---
 contrib/optrecord.py                               |  30 +++-
 gcc/testsuite/gcc.dg/dg-python-script-smoketest.c  |   4 +
 gcc/testsuite/gcc.dg/dg-python-script-smoketest.py |   6 +
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   3 +-
 gcc/testsuite/gcc.dg/plugin/test-dump.py           | 183 +++++++++++++++++++++
 gcc/testsuite/lib/dejagnu.py                       |  26 +++
 gcc/testsuite/lib/gcc-dg.exp                       |  30 ++++
 gcc/testsuite/lib/host-execute.exp                 |   2 +
 8 files changed, 277 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/dg-python-script-smoketest.c
 create mode 100755 gcc/testsuite/gcc.dg/dg-python-script-smoketest.py
 create mode 100755 gcc/testsuite/gcc.dg/plugin/test-dump.py
 create mode 100644 gcc/testsuite/lib/dejagnu.py

diff --git a/contrib/optrecord.py b/contrib/optrecord.py
index b07488e..9b21624 100755
--- a/contrib/optrecord.py
+++ b/contrib/optrecord.py
@@ -41,7 +41,7 @@ class TranslationUnit:
         metadata, passes, records = json_obj
 
         self.format = metadata['format']
-        self.generator = metadata['generator']
+        self.generator = Generator(metadata['generator'])
         self.passes = [Pass(obj, self) for obj in passes]
         self.records = [Record(obj, self) for obj in records]
 
@@ -49,6 +49,17 @@ class TranslationUnit:
         return ('TranslationUnit(%r, %r, %r, %r)'
                 % (self.filename, self.generator, self.passes, self.records))
 
+class Generator:
+    """Metadata about what created the file"""
+    def __init__(self, json_obj):
+        FIELDS = ['name', 'pkgversion', 'version', 'target']
+        for field in FIELDS:
+            setattr(self, field, json_obj[field])
+
+    def __repr__(self):
+        return ('Generator(%r, %r, %r, %r)'
+                % (self.name, self.pkgversion, self.version, self.target))
+
 class Pass:
     """An optimization pass"""
     def __init__(self, json_obj, tu):
@@ -131,8 +142,11 @@ class Record:
                          for child in json_obj.get('children', [])]
 
     def __repr__(self):
-        return ('Record(%r, %r, %r, %r, %r)'
-                % (self.kind, self.message, self.pass_, self.function,
+        return ('Record(kind=%r, pass_%r, function=%r, impl_location=%r,'
+                ' message=%r, count=%r, location=%r, inlining_chain=%r,'
+                ' children=%r)'
+                % (self.kind, self.pass_, self.function, self.impl_location,
+                   self.message, self.count, self.location, self.inlining_chain,
                    self.children))
 
 class InliningNode:
@@ -141,6 +155,10 @@ class InliningNode:
         self.fndecl = json_obj['fndecl']
         self.site = from_optional_json_field(Location, json_obj, 'site')
 
+    def __repr__(self):
+        return ('InliningNode(%r, %r)'
+                % (self.fndecl, self.site))
+
 class Item:
     """Base class for non-string items within a message"""
     @staticmethod
@@ -166,7 +184,7 @@ class Expr(Item):
         return self.expr
 
     def __repr__(self):
-        return 'Expr(%r)' % self.expr
+        return 'Expr(%r, %r)' % (self.expr, self.location)
 
 class Stmt(Item):
     """A statement within a message"""
@@ -178,7 +196,7 @@ class Stmt(Item):
         return self.stmt
 
     def __repr__(self):
-        return 'Stmt(%r)' % self.stmt
+        return 'Stmt(%r, %r)' % (self.stmt, self.location)
 
 class SymtabNode(Item):
     """A symbol table node within a message"""
@@ -190,7 +208,7 @@ class SymtabNode(Item):
         return self.node
 
     def __repr__(self):
-        return 'SymtabNode(%r)' % self.node
+        return 'SymtabNode(%r, %r)' % (self.node, self.location)
 
 ############################################################################
 
diff --git a/gcc/testsuite/gcc.dg/dg-python-script-smoketest.c b/gcc/testsuite/gcc.dg/dg-python-script-smoketest.c
new file mode 100644
index 0000000..1e9ab10
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/dg-python-script-smoketest.c
@@ -0,0 +1,4 @@
+/* { dg-do compile } */
+/* { dg-final { dg-python-script { "../../../../src/gcc/testsuite/gcc.dg/dg-python-script-smoketest.py" } } } */ /* FIXME */
+
+int non_empty;
diff --git a/gcc/testsuite/gcc.dg/dg-python-script-smoketest.py b/gcc/testsuite/gcc.dg/dg-python-script-smoketest.py
new file mode 100755
index 0000000..c9ace74
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/dg-python-script-smoketest.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+import dejagnu
+
+r = dejagnu.Reporter('')
+r.PASS('smoketest of PASS')
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index d92ede7..4aebc5d 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -104,7 +104,8 @@ set plugin_test_list [list \
 	  expensive-selftests-1.c } \
     { dump_plugin.c \
 	  dump-1.c \
-	  dump-2.c } \
+	  dump-2.c \
+	  dump-3.c } \
 ]
 
 foreach plugin_test $plugin_test_list {
diff --git a/gcc/testsuite/gcc.dg/plugin/test-dump.py b/gcc/testsuite/gcc.dg/plugin/test-dump.py
new file mode 100755
index 0000000..ba3c701
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/test-dump.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python3
+# Load the JSON file, and verify it
+import dejagnu
+import optrecord
+
+def assert_string_field(obj, fieldname):
+    assert hasattr(obj, fieldname)
+    assert isinstance(getattr(obj, fieldname), str)
+
+def assert_is_note(record):
+    assert record.kind == 'note'
+    assert record.children == []
+
+def assert_is_scope(record):
+    assert record.kind == 'scope'
+
+def assert_from_plugin(record):
+    assert record.pass_.name == 'test_dump'
+    assert record.impl_location.file.endswith('dump_plugin.c')
+    assert record.impl_location.function.endswith('execute')
+    assert record.location.file.endswith('dump-1.c')
+
+def assert_is_note_from_plugin(record):
+    assert_is_note(record)
+    assert_from_plugin(record)
+
+def assert_is_scope_from_plugin(record):
+    assert_is_scope(record)
+    assert_from_plugin(record)
+
+def assert_is_expr(item, text):
+    assert isinstance(item, optrecord.Expr)
+    assert item.expr == text
+
+def assert_is_stmt(item, text):
+    assert isinstance(item, optrecord.Stmt)
+    assert item.stmt == text
+
+def assert_is_stmt_from_plugin(item, text):
+    assert_is_stmt(item, text)
+    assert item.location is not None
+    assert item.location.file.endswith('dump-1.c')
+
+def assert_is_symtab_node(item, text):
+    assert isinstance(item, optrecord.SymtabNode)
+    assert item.node.startswith('%s/' % text)
+
+def assert_is_symtab_node_from_plugin(item, text):
+    assert_is_symtab_node(item, text)
+    assert item.location is not None
+    assert item.location.file.endswith('dump-1.c')
+
+r = dejagnu.Reporter('')
+
+# FIXME:
+FILENAME = '/home/david/coding-3/gcc-git-static-analysis/build/gcc/testsuite/gcc/dump-1.c.opt-record.json'
+
+r.NOTE('trying to load %s' % FILENAME)
+tu = optrecord.TranslationUnit.from_filename(FILENAME)
+r.PASS('loaded %s' % FILENAME)
+
+assert_string_field(tu, 'filename')
+
+# Verify tu.generator has the expected metadata fields
+assert hasattr(tu, 'generator')
+assert_string_field(tu.generator, 'name')
+assert_string_field(tu.generator, 'pkgversion')
+assert_string_field(tu.generator, 'version')
+assert_string_field(tu.generator, 'target')
+
+r.PASS('verified metadata')
+
+# Verify tu.passes
+assert hasattr(tu, 'passes')
+
+# TODO: really verify?
+#print(tu.pass_by_id)
+#print(tu.passes)
+#cfg_pass = tu.pass_by_id['cfg']
+#print(cfg_pass)
+
+#print(tu.passes)
+
+r.PASS('verified passes')
+
+# Verify the records themselves
+
+assert len(tu.records) == 8
+
+rec = tu.records[0]
+assert_is_note_from_plugin(rec)
+assert rec.message == ['test of remark for ', 'test_string_literal']
+assert rec.count == None
+#TODO: verify source location?
+# FIXME: inlining_chain?
+r.PASS('verified record 0')
+
+rec = tu.records[1]
+assert_is_note_from_plugin(rec)
+assert rec.message[0] == 'test of tree: '
+assert_is_expr(rec.message[1], '0')
+assert rec.count == None
+#TODO: verify source location?
+# FIXME: inlining_chain?
+r.PASS('verified record 1')
+
+rec = tu.records[2]
+assert_is_note_from_plugin(rec)
+assert rec.message[0] == 'test of gimple: '
+assert_is_stmt_from_plugin(rec.message[1], 'test_gimple (42);\n')
+assert rec.count == None
+#TODO: verify source location?
+# FIXME: inlining_chain?
+r.PASS('verified record 2')
+
+rec = tu.records[3]
+assert_is_note_from_plugin(rec)
+assert rec.message[0] == 'test of callgraph node: '
+assert_is_symtab_node_from_plugin(rec.message[1], 'test_cgraph_node')
+assert rec.count == None
+#TODO: verify source location?
+# FIXME: inlining_chain?
+r.PASS('verified record 3')
+
+rec = tu.records[4]
+assert_is_note_from_plugin(rec)
+assert rec.message == ['test of optinfo printf: 42']
+assert rec.count == None
+#TODO: verify source location?
+# FIXME: inlining_chain?
+r.PASS('verified record 4')
+
+rec = tu.records[5]
+assert_is_note_from_plugin(rec)
+assert rec.message == ['test of wide int: 0']
+assert rec.count == None
+#TODO: verify source location?
+# FIXME: inlining_chain?
+r.PASS('verified record 5')
+
+rec = tu.records[6]
+assert_is_note_from_plugin(rec)
+assert rec.message == ['test of poly int: ', '42']
+assert rec.count == None
+#TODO: verify source location?
+# FIXME: inlining_chain?
+r.PASS('verified record 6')
+
+rec = tu.records[7]
+assert_is_scope_from_plugin(rec)
+assert rec.message == ['=== outer scope ===\n']
+assert len(rec.children) == 2
+r.PASS('verified record 7')
+
+rec = tu.records[7].children[0]
+assert_is_note_from_plugin(rec)
+assert rec.message == ['at outer scope']
+r.PASS('verified record 7.0')
+
+rec = tu.records[7].children[1]
+assert_is_scope_from_plugin(rec)
+assert rec.message == ['=== middle scope ===\n']
+assert len(rec.children) == 2
+r.PASS('verified record 7.1')
+
+rec = tu.records[7].children[1].children[0]
+assert_is_note_from_plugin(rec)
+assert rec.message == ['at middle scope']
+r.PASS('verified record 7.1.0')
+
+rec = tu.records[7].children[1].children[1]
+assert_is_scope_from_plugin(rec)
+assert rec.message == ['=== innermost scope ===\n']
+assert len(rec.children) == 1
+r.PASS('verified record 7.1.1')
+
+rec = tu.records[7].children[1].children[1].children[0]
+assert_is_note_from_plugin(rec)
+assert rec.message == ['at innermost scope']
+r.PASS('verified record 7.1.1.0')
+
+assert len(tu.records) == 8
+r.PASS('verified all records')
diff --git a/gcc/testsuite/lib/dejagnu.py b/gcc/testsuite/lib/dejagnu.py
new file mode 100644
index 0000000..6af65d4
--- /dev/null
+++ b/gcc/testsuite/lib/dejagnu.py
@@ -0,0 +1,26 @@
+# FIXME: copyright
+
+import sys
+
+class Reporter:
+    """
+    dejagnu.exp's host_execute looks for output messages on stdout
+    in the format emitted by dejagnu.h:
+    "\tNOTE: text\n": handled as level 3 verbose output
+    "\tPASSED: text\n": handled as "PASS"
+    "\tFAILED: text\n":  handled as "FAIL"
+    """
+    def __init__(self, prefix):
+        self.prefix = prefix
+
+    def result(self, kind, msg):
+        sys.stdout.write('\t%s: %s%s\n' % (kind, self.prefix, msg))
+
+    def PASS(self, msg):
+        self.result('PASSED', msg)
+
+    def FAIL(self, msg):
+        self.result('FAILED', msg)
+
+    def NOTE(self, msg):
+        self.result('NOTE', msg)
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index c33a50c..ee61a38 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -33,6 +33,7 @@ load_lib target-libpath.exp
 load_lib torture-options.exp
 load_lib fortran-modules.exp
 load_lib multiline.exp
+load_lib host-execute.exp
 
 # We set LC_ALL and LANG to C so that we get the same error messages as expected.
 setenv LC_ALL C
@@ -1236,5 +1237,34 @@ proc gdb-exists { args } {
     return 0;
 }
 
+# FIXME
+
+proc dg-host-execute { dgargs } {
+    verbose "dg-host-execute: ${dgargs}" 2
+    set script [lindex $dgargs 0]
+    verbose "  script: ${script}" 2
+
+    set result [fixed_host_execute ${script}]
+    verbose "  result: ${result}" 2
+    
+    # FIXME: what should the PATH be?
+}
+
+proc dg-python-script { dgargs } {
+    verbose "dg-python-script: ${dgargs}" 2
+    set script [lindex $dgargs 0]
+    verbose "  script: ${script}" 2
+
+    # FIXME: check for presence of Python 3, issuing UNSUPPORTED if not found
+
+    # Set PYTHONPATH to this directory, and to "contrib"
+    # FIXME: append to env, and only for subprocess
+    global srcdir
+    verbose " srcdir: $srcdir" 3
+    setenv PYTHONPATH "${srcdir}/lib:${srcdir}/../../contrib"
+
+    dg-host-execute $dgargs
+}
+
 set additional_prunes ""
 set dg_runtest_extra_prunes ""
diff --git a/gcc/testsuite/lib/host-execute.exp b/gcc/testsuite/lib/host-execute.exp
index 1168736..2d5fe84 100644
--- a/gcc/testsuite/lib/host-execute.exp
+++ b/gcc/testsuite/lib/host-execute.exp
@@ -1,3 +1,5 @@
+# FIXME: copyright
+
 # FIXME: taken from jit.exp
 
 # Look for lines of the form:
-- 
1.8.5.3

