test/py: HTML awesome!

Implement three improvements to the HTML log file:
- Ability to expand/contract sections. All passing sections are contracted
  at file load time so the user can concentrate on issues requiring
  action.
- The overall status report is copied to the top of the log for easy
  access.
- Add links from the status report to the test logs, for easy navigation.

This all relies on Javascript and the jquery library. If the user doesn't
have Javascript enabled, or jquery can't be downloaded, the log should
look and behave identically to how it did before this patch.

A few notes on the diff:

- A few more 'with log.section("xxx")' were added, so that all stream
  blocks are kept within a section block for consistent HTML entity
  nesting structure. This changed indentation in a few places, making
  the diff look slightly larger.
- HTML entity IDs are cleaned up. We assign simple incrementing integer
  IDs now, rather than using mangled test names which were possibly
  invalid.
- Sections and streams now use common CSS class names (in addition to the
  current separate class names) to more easily share the new behaviour.
  This also reduces the CSS file size since rules don't need to be
  duplicated.
- An "OK" status is logged after some external command executions so that
  make and flash steps are auto-contracted at log file load time, assuming
  they passed.

Signed-off-by: Stephen Warren <swarren@nvidia.com>
diff --git a/test/py/conftest.py b/test/py/conftest.py
index 09638e6..3012c8e 100644
--- a/test/py/conftest.py
+++ b/test/py/conftest.py
@@ -129,10 +129,12 @@
             ['make', o_opt, '-s', board_type + '_defconfig'],
             ['make', o_opt, '-s', '-j8'],
         )
-        runner = log.get_runner('make', sys.stdout)
-        for cmd in cmds:
-            runner.run(cmd, cwd=source_dir)
-        runner.close()
+        with log.section('make'):
+            runner = log.get_runner('make', sys.stdout)
+            for cmd in cmds:
+                runner.run(cmd, cwd=source_dir)
+            runner.close()
+            log.status_pass('OK')
 
     class ArbitraryAttributeContainer(object):
         pass
@@ -255,6 +257,7 @@
     console.ensure_spawned()
     return console
 
+anchors = {}
 tests_not_run = set()
 tests_failed = set()
 tests_xpassed = set()
@@ -294,27 +297,33 @@
     if console:
         console.close()
     if log:
-        log.status_pass('%d passed' % len(tests_passed))
-        if tests_skipped:
-            log.status_skipped('%d skipped' % len(tests_skipped))
-            for test in tests_skipped:
-                log.status_skipped('... ' + test)
-        if tests_xpassed:
-            log.status_xpass('%d xpass' % len(tests_xpassed))
-            for test in tests_xpassed:
-                log.status_xpass('... ' + test)
-        if tests_xfailed:
-            log.status_xfail('%d xfail' % len(tests_xfailed))
-            for test in tests_xfailed:
-                log.status_xfail('... ' + test)
-        if tests_failed:
-            log.status_fail('%d failed' % len(tests_failed))
-            for test in tests_failed:
-                log.status_fail('... ' + test)
-        if tests_not_run:
-            log.status_fail('%d not run' % len(tests_not_run))
-            for test in tests_not_run:
-                log.status_fail('... ' + test)
+        with log.section('Status Report', 'status_report'):
+            log.status_pass('%d passed' % len(tests_passed))
+            if tests_skipped:
+                log.status_skipped('%d skipped' % len(tests_skipped))
+                for test in tests_skipped:
+                    anchor = anchors.get(test, None)
+                    log.status_skipped('... ' + test, anchor)
+            if tests_xpassed:
+                log.status_xpass('%d xpass' % len(tests_xpassed))
+                for test in tests_xpassed:
+                    anchor = anchors.get(test, None)
+                    log.status_xpass('... ' + test, anchor)
+            if tests_xfailed:
+                log.status_xfail('%d xfail' % len(tests_xfailed))
+                for test in tests_xfailed:
+                    anchor = anchors.get(test, None)
+                    log.status_xfail('... ' + test, anchor)
+            if tests_failed:
+                log.status_fail('%d failed' % len(tests_failed))
+                for test in tests_failed:
+                    anchor = anchors.get(test, None)
+                    log.status_fail('... ' + test, anchor)
+            if tests_not_run:
+                log.status_fail('%d not run' % len(tests_not_run))
+                for test in tests_not_run:
+                    anchor = anchors.get(test, None)
+                    log.status_fail('... ' + test, anchor)
         log.close()
 atexit.register(cleanup)
 
@@ -380,7 +389,7 @@
         Nothing.
     """
 
-    log.start_section(item.name)
+    anchors[item.name] = log.start_section(item.name)
     setup_boardspec(item)
     setup_buildconfigspec(item)