#!/usr/bin/python
# A test-suite for the C++ syntax highlighter

import os, types, sys, string, re, urllib

class config:
  gdb = 0
  dump = 0
  regression = 0

gcc_include2 = "-I/usr/include/g++-3/ -I/usr/lib/gcc-lib/i386-linux/2.95.4/include "
gcc_include3 = "-I/usr/include/g++-v3/ -I/usr/include/g++-v3/i386-linux/ -I/usr/lib/gcc-lib/i386-linux/3.0.1/include "
gcc_include = gcc_include2
python_include = "\"-DPYTHON_INCLUDE=<python2.1/Python.h>\" "


html_top = '<html><link rel="stylesheet" href="/home/chalky/src/Synopsis/demo/html.css"><body>'
class Test:
  flags = ""
  str_make_debug = "(cd ../syn; make debug)"
  str_make = "(cd ../syn; make -s)"

  def run(self):
    "Return false if test failed"
    test_text = self.__class__.test
    test_base = self.__class__.__name__
    test_file = "/tmp/%s.cc"%test_base
    f = open(test_file, "w")
    f.write(test_text)
    f.close()
    return self.do_run(test_file, test_base)

  def do_run(self, test_file, test_base, flags=""):
    global config
    flags = flags + self.flags
    if config.regression:
      return not self.run_regression(test_file, test_base, flags)
    elif config.gdb:
      return not self.gdb_less(test_file, flags)
    elif config.dump:
      return not self.dump_less(test_file, flags)
    else:
      return not self.view_page(test_file, test_base, flags)

  def system(self, command):
    """Runs the given command using system(). Returns the error code of the
    command run, which will be 0 for no error by convention."""
    ret = os.system(command)
    return ret >> 8

  def gdb_less(self, test_file, flags = ""):
    return self.system(Test.str_make_debug + " && "\
      "echo '../syn/occ.gdb %s %s' && "\
      "../syn/occ.gdb %s %s 2>&1 | less"%(
        flags, test_file, flags, test_file))

  def dump_less(self, test_file, flags = ""):
    return self.system(Test.str_make + " && "\
      "echo synopsis -v -Wc,parser=C++,formatter=DUMP %(flags)s %(test_file)s &&"\
      "synopsis -v -Wc,parser=C++,formatter=DUMP -Wf,-d,-t,-f %(flags)s %(test_file)s "\
      "2>&1 | less -r"%vars())

  def _do_synopsis(self, file, base, flags=""):
    """Runs synopsis on the input file. A Links file (.links) and AST file
    (.syn) are generated."""
    syn_file = "/tmp/"+base+".syn"
    links_file = "/tmp/"+base+".links"
    if config.regression: verbose = ""
    else: verbose = "-v"
    synopsis_flags = string.join([verbose,  # Verbose
      "-Wc,parser=C++",                  # Use C++ Parser
      "-Wp,-t,-s,%(links_file)s",        # Tags and links
      "%(flags)s",                       # Other flags
      "-o %(syn_file)s",                 # Output file
      "%(file)s"], " ")%vars()           # Input file

    commands = [Test.str_make % vars()]
    if not config.regression: commands.append("echo \"Running Synopsis...\"")
    commands.append("synopsis %(synopsis_flags)s"%vars())
      
    return self.system(string.join(commands, " && "))

  def _do_cleanup(self, file, base):
    """Cleans up the files generated by _do_synopsis"""
    syn_file = "/tmp/"+base+".syn"
    links_file = "/tmp/"+base+".links"
    return 0
    return self.system("rm -f /tmp/%(base)s.{links,syn,html.out,top}"%vars() )


  def _do_html_view(self, file, base):
    """Launches a web browser to view the html output"""
    f = open("/tmp/%s.top"%base, "w")
    f.write(html_top)
    f.close()
    syn_file = "/tmp/"+base+".syn"
    links_file = "/tmp/"+base+".links"
    return self.system("echo \"Running Linker...\" && "\
      "../syn/link-synopsis -i %(file)s -l /tmp/%(base)s.links -o /tmp/%(base)s.html.out && "\
      "(cat /tmp/%(base)s.top /tmp/%(base)s.html.out > /tmp/%(base)s.html) && "\
      "web /tmp/%(base)s.html"%vars() )

  def view_page(self, file, base, flags=""):
    """Runs synopsis on the given page and views the output as HTML"""
    error = self._do_synopsis(file, base, flags)
    if not error: error = self._do_html_view(file, base)
    if not error: error = self._do_cleanup(file, base)
    else: self._do_cleanup(file, base) # Cleanup anyway
    return error

  def run_regression(self, file, base, flags=""):
    """Runs synopsis on the given page and performs tests on the output"""
    error = self._do_synopsis(file, base, flags)
    if not error: error = self.test_links(file, base)
    if not error: error = self._do_cleanup(file, base)
    return error


class Regression:
  """A regression test identifies a test that takes part in regression
  testing"""

  re_test = re.compile(r'test ([a-zA-Z_0-9]+) = "([^"]*)"')
  
  def test_links(self, file, base):
    """Tests the links in the test to make sure Synopsis is working okay"""

    # Read links file and split into a map
    links = {}
    links_file = "/tmp/"+base+".links"
    flinks = open(links_file, "r")
    for line in flinks.readlines():
      lnum, start, length, ltype, rest = string.split(line, ' ', 4)
      if ltype not in ("REF", "CALL"): continue
      ref, comment = string.split(rest)
      links.setdefault(int(lnum), []).append( (int(start), int(length), ref) )
    flinks.close()


    # Loop through the lines in the test case, looking for "test = " comments
    linenum = 0
    for line in self.test.split('\n'):
      linenum = linenum + 1

      # Find comment
      pos = line.find('// ')
      if pos < 0: continue
      text = line[pos+3:]

      # Loop to find tests
      mo = self.re_test.search(text)
      while mo:
        # Get name of variable and reference it should point to
        testname, testref = mo.groups()

        found = 0
        # Find variable in links map
        for start, length, ref in links[linenum]:
          # un-encode ref (note this is done in the less-often case of being
          # tested, not for every ref at read time!)
          ref = string.replace(ref, '%09', '::')
          ref = urllib.unquote_plus(ref)
          name = line[start-1:start-1+length]
          #print "Testing",name,"=",testname,"and",ref,"=",testref
          if name == testname:
            # Found it! (assumes one variable with this name per line)
            if ref != testref:
              print "\nError: reference",testref,"not found on line",linenum
              print "       found '%s' instead."%ref
              return 1
            found = 1
            break

        # Check that a link was found which matched the name
        if not found:
          print "\nError: name",testname,"not found on line",linenum
          print links[linenum]
          return 1

        # Find next testcase (if any) on this line
        mo = self.re_test.search(text, mo.end())
        

# Note: Regression tests are defined in regressions.py

class Link (Test):
  def run(self):
    return self.do_run("../syn/link.cc", "link", gcc_include + python_include)

class Builder (Test):
  def run(self):
    return self.do_run("../syn/builder.cc", "builder", gcc_include + python_include + "-I../")

class Synopsis (Test):
  def run(self):
    return self.do_run("../syn/synopsis.cc", "synopsis", gcc_include + python_include)

class AST (Test):
  def run(self):
    return self.do_run("../syn/ast.hh", "ast", gcc_include + python_include)

class TestTemplate (Test):
  def run(self):
    return self.do_run("test-template.cc", "test-template", gcc_include)

class IterativeInclude (Test):
  def run(self):
    return self.do_run("iterative_include.h", "iterative_include", "")

class RegressionTest (Test):
  def run(self):
    # Find tests
    tests = []
    for key, obj in regressions.__dict__.items():
      if type(obj) is types.ClassType:
        if Regression in obj.__bases__:
          tests.append(obj)

    # Run tests
    passed = 0
    for test in tests:
      print "  %-40s .. "%(test.__name__,),
      result = test().run()
      if result:
        print "passed"
        passed = passed + 1
      else:
        print "failed"

    # Print summary
    print " %d of %d tests passed."%(passed, len(tests))

    return passed == len(tests)
    
import regressions
from regressions import *
 
if __name__ == "__main__":
  tests = {}
  for key in globals().keys():
    obj = globals()[key]
    if type(obj) is types.ClassType:
      if Test in obj.__bases__:
        tests[key] = obj
  test = None
  for arg in sys.argv[1:]:
    if arg[0] == '-':
      if arg[1] == 'g':
        config.gdb = 1
      elif arg[1] == 'r':
        test = "RegressionTest"
        config.regression = 1
      elif arg == '--dump':
        config.dump = 1
      else:
        print "Unknown argument:",arg
    else:
      test = arg
  if not tests.has_key(test): test = None
  if test is None:
    print "Choose a test from:", string.join(tests.keys())
    sys.exit(1)
  test = tests[test]
  if not test().run(): print "failed"

# vim: set et sts=2 ts=8 sw=2:
