[gjs: 3/6] coverage: Use LCOV data from SpiderMonkey



commit b7c934355c02909e444597dd0f0d189d9ad613df
Author: Philip Chimento <philip chimento gmail com>
Date:   Mon Oct 2 11:13:42 2017 -0700

    coverage: Use LCOV data from SpiderMonkey
    
    SpiderMonkey now provides its own coverage data conveniently already
    formatted as LCOV output. It does have a few quirks and so requires a bit
    of adjustment in the tests, but it allows us to drop a few thousand lines
    of custom code.
    
    Differences between the old LCOV data and the new:
    
     - There is always a function named "top-level" in each file.
     - Anything on line 1 seems to be ignored.
     - Branches can appear in different order.
     - There's no difference between not-taken and not-executed branches.
       They always appear as not-executed.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=788166

 Makefile-test.am                                   |   29 -
 gjs-srcs.mk                                        |    1 -
 gjs/coverage-internal.h                            |   68 -
 gjs/coverage.cpp                                   | 1425 ++------------------
 installed-tests/js/testCoverage.js                 | 1178 ----------------
 modules/_bootstrap/coverage.js                     | 1009 +-------------
 test/gjs-test-coverage.cpp                         |  943 +------------
 .../mock-js-resource-cache-after.gresource.xml     |    8 -
 .../cache_invalidation/after/resource.js           |    2 -
 .../mock-js-resource-cache-before.gresource.xml    |    8 -
 .../cache_invalidation/before/resource.js          |    2 -
 .../cache_notation/simple_branch.js                |    6 -
 .../cache_notation/simple_executable_lines.js      |    1 -
 .../cache_notation/simple_function.js              |    2 -
 test/mock-js-resources.gresource.xml               |    3 -
 15 files changed, 119 insertions(+), 4566 deletions(-)
---
diff --git a/Makefile-test.am b/Makefile-test.am
index 879363c..813a225 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -30,25 +30,6 @@ mock-js-resources.h: $(srcdir)/test/mock-js-resources.gresource.xml $(modules_re
 mock-js-resources.c: $(srcdir)/test/mock-js-resources.gresource.xml $(modules_resource_files)
        $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) --sourcedir=$(builddir) 
--generate --c-name mock_js_resources $<
 
-mock_js_invalidation_resources_dir = $(top_srcdir)/test/gjs-test-coverage/cache_invalidation
-mock_js_invalidation_before_resources_files =                                                          \
-       $(mock_js_invalidation_resources_dir)/before/resource.js                                        \
-       $(mock_js_invalidation_resources_dir)/before/mock-js-resource-cache-before.gresource.xml        \
-       $(NULL)
-mock_js_invalidation_after_resources_files =                                                           \
-       $(mock_js_invalidation_resources_dir)/after/resource.js                                         \
-       $(mock_js_invalidation_resources_dir)/after/mock-js-resource-cache-after.gresource.xml          \
-       $(NULL)
-mock_js_invalidation_resources_files =                 \
-       $(mock_js_invalidation_before_resources_files)  \
-       $(mock_js_invalidation_after_resources_files)   \
-       $(NULL)
-
-mock-cache-invalidation-before.gresource: $(mock_js_invalidation_before_resources_files)
-       $(AM_V_GEN) glib-compile-resources --target=$@ 
--sourcedir=$(mock_js_invalidation_resources_dir)/before 
$(mock_js_invalidation_resources_dir)/before/mock-js-resource-cache-before.gresource.xml
-mock-cache-invalidation-after.gresource: $(mock_js_invalidation_after_resources_files)
-       $(AM_V_GEN) glib-compile-resources --target=$@ 
--sourcedir=$(mock_js_invalidation_resources_dir)/after 
$(mock_js_invalidation_resources_dir)/after/mock-js-resource-cache-after.gresource.xml
-
 jsunit_resources_files := $(shell glib-compile-resources --sourcedir=$(srcdir)/installed-tests/js 
--generate-dependencies $(srcdir)/installed-tests/js/jsunit.gresources.xml)
 jsunit-resources.h: $(srcdir)/installed-tests/js/jsunit.gresources.xml $(jsunit_resources_files)
        $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir)/installed-tests/js 
--sourcedir=$(builddir) --generate --c-name jsunit_resources $<
@@ -59,15 +40,11 @@ BUILT_SOURCES += mock-js-resources.c jsunit-resources.h jsunit-resources.c
 
 EXTRA_DIST +=                                                  \
        $(mock_js_resources_files)                              \
-       $(mock_js_invalidation_resources_files)                 \
        $(srcdir)/test/mock-js-resources.gresource.xml          \
-       $(srcdir)/test/gjs-test-coverage/loadedJSFromResource.js \
        $(jsunit_resources_files)                               \
        $(NULL)
 
 CLEANFILES +=                                          \
-       mock-cache-invalidation-before.gresource        \
-       mock-cache-invalidation-after.gresource         \
        mock-js-resources.c                             \
        jsunit-resources.c                              \
        jsunit-resources.h                              \
@@ -105,11 +82,6 @@ gjs_tests_gtester_SOURCES =                         \
        mock-js-resources.c                             \
        $(NULL)
 
-gjs_tests_gtester_DEPENDENCIES =                       \
-       mock-cache-invalidation-before.gresource        \
-       mock-cache-invalidation-after.gresource         \
-       $(NULL)
-
 minijasmine_SOURCES =                  \
        installed-tests/minijasmine.cpp \
        jsunit-resources.c              \
@@ -218,7 +190,6 @@ CLEANFILES += $(TEST_INTROSPECTION_GIRS) $(TEST_INTROSPECTION_TYPELIBS)
 common_jstests_files =                                         \
        installed-tests/js/testself.js                          \
        installed-tests/js/testByteArray.js                     \
-       installed-tests/js/testCoverage.js                      \
        installed-tests/js/testExceptions.js                    \
        installed-tests/js/testEverythingBasic.js               \
        installed-tests/js/testEverythingEncapsulated.js        \
diff --git a/gjs-srcs.mk b/gjs-srcs.mk
index 36f29a7..1c61c60 100644
--- a/gjs-srcs.mk
+++ b/gjs-srcs.mk
@@ -52,7 +52,6 @@ gjs_srcs =                            \
        gjs/byteArray.h                 \
        gjs/context.cpp                 \
        gjs/context-private.h           \
-       gjs/coverage-internal.h         \
        gjs/coverage.cpp                \
        gjs/engine.cpp                  \
        gjs/engine.h                    \
diff --git a/gjs/coverage.cpp b/gjs/coverage.cpp
index d79fad3..cbe8be3 100644
--- a/gjs/coverage.cpp
+++ b/gjs/coverage.cpp
@@ -29,7 +29,6 @@
 #include <gjs/context.h>
 
 #include "coverage.h"
-#include "coverage-internal.h"
 #include "global.h"
 #include "importer.h"
 #include "jsapi-util-args.h"
@@ -42,12 +41,9 @@ struct _GjsCoverage {
 typedef struct {
     gchar **prefixes;
     GjsContext *context;
-    JS::Heap<JSObject *> coverage_statistics;
+    JS::Heap<JSObject *> compartment;
 
     GFile *output_dir;
-    GFile *cache;
-    /* tells whether priv->cache == NULL means no cache, or not specified */
-    bool cache_specified;
 } GjsCoveragePrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE(GjsCoverage,
@@ -65,23 +61,6 @@ enum {
 
 static GParamSpec *properties[PROP_N] = { NULL, };
 
-typedef struct _GjsCoverageBranchExit {
-    unsigned int line;
-    unsigned int hit_count;
-} GjsCoverageBranchExit;
-
-typedef struct _GjsCoverageBranch {
-    GArray       *exits;
-    unsigned int point;
-    bool         hit;
-} GjsCoverageBranch;
-
-typedef struct _GjsCoverageFunction {
-    char         *key;
-    unsigned int line_number;
-    unsigned int hit_count;
-} GjsCoverageFunction;
-
 static char *
 get_file_identifier(GFile *source_file) {
     char *path = g_file_get_path(source_file);
@@ -99,197 +78,6 @@ write_source_file_header(GOutputStream *stream,
     g_free(path);
 }
 
-typedef struct _FunctionHitCountData {
-    GOutputStream *stream;
-    unsigned int  *n_functions_found;
-    unsigned int  *n_functions_hit;
-} FunctionHitCountData;
-
-static void
-write_function_hit_count(GOutputStream *stream,
-                         const char    *function_name,
-                         unsigned int   hit_count,
-                         unsigned int  *n_functions_found,
-                         unsigned int  *n_functions_hit)
-{
-    (*n_functions_found)++;
-
-    if (hit_count > 0)
-        (*n_functions_hit)++;
-
-    g_output_stream_printf(stream, NULL, NULL, NULL, "FNDA:%d,%s\n", hit_count, function_name);
-}
-
-static void
-write_functions_hit_counts(GOutputStream *stream,
-                           GArray        *functions,
-                           unsigned int  *n_functions_found,
-                           unsigned int  *n_functions_hit)
-{
-    unsigned int i = 0;
-
-    for (; i < functions->len; ++i) {
-        GjsCoverageFunction *function = &(g_array_index(functions, GjsCoverageFunction, i));
-        write_function_hit_count(stream,
-                                 function->key,
-                                 function->hit_count,
-                                 n_functions_found,
-                                 n_functions_hit);
-    }
-}
-
-static void
-write_function_foreach_func(gpointer value,
-                            gpointer user_data)
-{
-    GOutputStream       *stream = (GOutputStream *) user_data;
-    GjsCoverageFunction *function = (GjsCoverageFunction *) value;
-
-    g_output_stream_printf(stream, NULL, NULL, NULL, "FN:%d,%s\n", function->line_number, function->key);
-}
-
-static void
-for_each_element_in_array(GArray   *array,
-                          GFunc     func,
-                          gpointer  user_data)
-{
-    const gsize element_size = g_array_get_element_size(array);
-    unsigned int i;
-    char         *current_array_pointer = (char *) array->data;
-
-    for (i = 0; i < array->len; ++i, current_array_pointer += element_size)
-        (*func)(current_array_pointer, user_data);
-}
-
-static void
-write_functions(GOutputStream *data_stream,
-                GArray        *functions)
-{
-    for_each_element_in_array(functions, write_function_foreach_func, data_stream);
-}
-
-static void
-write_function_coverage(GOutputStream *data_stream,
-                        unsigned int  n_found_functions,
-                        unsigned int  n_hit_functions)
-{
-    g_output_stream_printf(data_stream, NULL, NULL, NULL, "FNF:%d\n", n_found_functions);
-    g_output_stream_printf(data_stream, NULL, NULL, NULL, "FNH:%d\n", n_hit_functions);
-}
-
-typedef struct _WriteAlternativeData {
-    unsigned int  *n_branch_alternatives_found;
-    unsigned int  *n_branch_alternatives_hit;
-    GOutputStream *output_stream;
-    gpointer      *all_alternatives;
-    bool           branch_point_was_hit;
-} WriteAlternativeData;
-
-typedef struct _WriteBranchInfoData {
-    unsigned int *n_branch_exits_found;
-    unsigned int *n_branch_exits_hit;
-    GOutputStream *output_stream;
-} WriteBranchInfoData;
-
-static void
-write_individual_branch(gpointer branch_ptr,
-                        gpointer user_data)
-{
-    GjsCoverageBranch   *branch = (GjsCoverageBranch *) branch_ptr;
-    WriteBranchInfoData *data = (WriteBranchInfoData *) user_data;
-
-    /* This line is not a branch, don't write anything */
-    if (!branch->point)
-        return;
-
-    unsigned int i = 0;
-    for (; i < branch->exits->len; ++i) {
-        GjsCoverageBranchExit *exit = &(g_array_index(branch->exits, GjsCoverageBranchExit, i));
-        unsigned int alternative_counter = exit->hit_count;
-        unsigned int branch_point = branch->point;
-        char         *hit_count_string = NULL;
-
-        if (!branch->hit)
-            hit_count_string = g_strdup_printf("-");
-        else
-            hit_count_string = g_strdup_printf("%d", alternative_counter);
-
-        g_output_stream_printf(data->output_stream, NULL, NULL, NULL, "BRDA:%d,0,%d,%s\n",
-                               branch_point, i, hit_count_string);
-        g_free(hit_count_string);
-
-        ++(*data->n_branch_exits_found);
-
-        if (alternative_counter > 0)
-            ++(*data->n_branch_exits_hit);
-    }
-}
-
-static void
-write_branch_coverage(GOutputStream *stream,
-                      GArray        *branches,
-                      unsigned int  *n_branch_exits_found,
-                      unsigned int  *n_branch_exits_hit)
-
-{
-    /* Write individual branches and pass-out the totals */
-    WriteBranchInfoData data = {
-        n_branch_exits_found,
-        n_branch_exits_hit,
-        stream
-    };
-
-    for_each_element_in_array(branches,
-                              write_individual_branch,
-                              &data);
-}
-
-static void
-write_branch_totals(GOutputStream *stream,
-                    unsigned int   n_branch_exits_found,
-                    unsigned int   n_branch_exits_hit)
-{
-    g_output_stream_printf(stream, NULL, NULL, NULL, "BRF:%d\n", n_branch_exits_found);
-    g_output_stream_printf(stream, NULL, NULL, NULL, "BRH:%d\n", n_branch_exits_hit);
-}
-
-static void
-write_line_coverage(GOutputStream *stream,
-                    GArray        *stats,
-                    unsigned int  *lines_hit_count,
-                    unsigned int  *executable_lines_count)
-{
-    unsigned int i = 0;
-    for (i = 0; i < stats->len; ++i) {
-        int hit_count_for_line = g_array_index(stats, int, i);
-
-        if (hit_count_for_line == -1)
-            continue;
-
-        g_output_stream_printf(stream, NULL, NULL, NULL, "DA:%d,%d\n", i, hit_count_for_line);
-
-        if (hit_count_for_line > 0)
-            ++(*lines_hit_count);
-
-        ++(*executable_lines_count);
-    }
-}
-
-static void
-write_line_totals(GOutputStream *stream,
-                  unsigned int   lines_hit_count,
-                  unsigned int   executable_lines_count)
-{
-    g_output_stream_printf(stream, NULL, NULL, NULL, "LH:%d\n", lines_hit_count);
-    g_output_stream_printf(stream, NULL, NULL, NULL, "LF:%d\n", executable_lines_count);
-}
-
-static void
-write_end_of_record(GOutputStream *stream)
-{
-    g_output_stream_printf(stream, NULL, NULL, NULL, "end_of_record\n");
-}
-
 static void
 copy_source_file_to_coverage_output(GFile *source_file,
                                     GFile *destination_file)
@@ -398,784 +186,31 @@ find_diverging_child_components(GFile *child,
     return stripped_uri;
 }
 
-typedef bool (*ConvertAndInsertJSVal) (GArray         *array,
-                                       JSContext      *context,
-                                       JS::HandleValue element);
-
 static bool
-get_array_from_js_value(JSContext             *context,
-                        JS::HandleValue        value,
-                        size_t                 array_element_size,
-                        GDestroyNotify         element_clear_func,
-                        ConvertAndInsertJSVal  inserter,
-                        GArray                **out_array)
+filename_has_coverage_prefixes(GjsCoverage *self, const char *filename)
 {
-    g_return_val_if_fail(out_array != NULL, false);
-    g_return_val_if_fail(*out_array == NULL, false);
-
-    bool is_array;
-    if (!JS_IsArrayObject(context, value, &is_array))
-        return false;
-    if (!is_array) {
-        g_critical("Returned object from is not an array");
-        return false;
-    }
-
-    /* We're not preallocating any space here at the moment until
-     * we have some profiling data that suggests a good size to
-     * preallocate to. */
-    GArray *c_side_array = g_array_new(true, true, array_element_size);
-    uint32_t js_array_len;
-    JS::RootedObject js_array(context, &value.toObject());
-
-    if (element_clear_func)
-        g_array_set_clear_func(c_side_array, element_clear_func);
-
-    if (JS_GetArrayLength(context, js_array, &js_array_len)) {
-        uint32_t i = 0;
-        JS::RootedValue element(context);
-        for (; i < js_array_len; ++i) {
-            if (!JS_GetElement(context, js_array, i, &element)) {
-                g_array_unref(c_side_array);
-                gjs_throw(context, "Failed to get function names array element %d", i);
-                return false;
-            }
+    auto priv = static_cast<GjsCoveragePrivate *>(gjs_coverage_get_instance_private(self));
 
-            if (!(inserter(c_side_array, context, element))) {
-                g_array_unref(c_side_array);
-                gjs_throw(context, "Failed to convert array element %d", i);
-                return false;
-            }
-        }
+    for (const char * const *prefix = priv->prefixes; *prefix; prefix++) {
+        if (g_str_has_prefix(filename, *prefix))
+            return true;
     }
-
-    *out_array = c_side_array;
-
-    return true;
+    return false;
 }
 
 static bool
-convert_and_insert_unsigned_int(GArray         *array,
-                                JSContext      *context,
-                                JS::HandleValue element)
-{
-    if (!element.isInt32() && !element.isUndefined() && !element.isNull()) {
-        g_critical("Array element is not an integer or undefined or null");
-        return false;
-    }
-
-    if (element.isInt32()) {
-        unsigned int element_integer = element.toInt32();
-        g_array_append_val(array, element_integer);
-    } else {
-        int not_executable = -1;
-        g_array_append_val(array, not_executable);
-    }
-
-    return true;
-}
-
-static GArray *
-get_executed_lines_for(JSContext        *context,
-                       JS::HandleObject  coverage_statistics,
-                       JS::HandleValue   filename_value)
-{
-    GArray *array = NULL;
-    JS::RootedValue rval(context);
-    JS::AutoValueArray<1> args(context);
-    args[0].set(filename_value);
-
-    if (!JS_CallFunctionName(context, coverage_statistics, "getExecutedLinesFor",
-                             args, &rval)) {
-        gjs_log_exception(context);
-        return NULL;
-    }
-
-    if (!get_array_from_js_value(context, rval, sizeof(unsigned int), NULL,
-        convert_and_insert_unsigned_int, &array)) {
-        gjs_log_exception(context);
-        return NULL;
-    }
-
-    return array;
-}
-
-static void
-init_covered_function(GjsCoverageFunction *function,
-                      const char          *key,
-                      unsigned int        line_number,
-                      unsigned int        hit_count)
-{
-    function->key = g_strdup(key);
-    function->line_number = line_number;
-    function->hit_count = hit_count;
-}
-
-static void
-clear_coverage_function(gpointer info_location)
-{
-    GjsCoverageFunction *info = (GjsCoverageFunction *) info_location;
-    g_free(info->key);
-}
-
-static bool
-get_hit_count_and_line_data(JSContext       *cx,
-                            JS::HandleObject obj,
-                            const char      *description,
-                            int32_t         *hit_count,
-                            int32_t         *line)
-{
-    JS::RootedId hit_count_name(cx, gjs_intern_string_to_id(cx, "hitCount"));
-    if (!gjs_object_require_property(cx, obj, "function element",
-                                     hit_count_name, hit_count))
-        return false;
-
-    JS::RootedId line_number_name(cx, gjs_intern_string_to_id(cx, "line"));
-    return gjs_object_require_property(cx, obj, "function_element",
-                                       line_number_name, line);
-}
-
-static bool
-convert_and_insert_function_decl(GArray         *array,
-                                 JSContext      *context,
-                                 JS::HandleValue element)
-{
-    if (!element.isObject()) {
-        gjs_throw(context, "Function element is not an object");
-        return false;
-    }
-
-    JS::RootedObject object(context, &element.toObject());
-    JS::RootedValue function_name_property_value(context);
-
-    if (!gjs_object_require_property(context, object, NULL, GJS_STRING_NAME,
-                                     &function_name_property_value))
-        return false;
-
-    GjsAutoJSChar utf8_string(context);
-
-    if (function_name_property_value.isString()) {
-        if (!gjs_string_to_utf8(context,
-                                function_name_property_value,
-                                &utf8_string)) {
-            gjs_throw(context, "Failed to convert function_name to string");
-            return false;
-        }
-    } else if (!function_name_property_value.isNull()) {
-        gjs_throw(context, "Unexpected type for function_name");
-        return false;
-    }
-
-    int32_t hit_count;
-    int32_t line_number;
-    if (!get_hit_count_and_line_data(context, object, "function element",
-                                     &hit_count, &line_number))
-        return false;
-
-    GjsCoverageFunction info;
-    init_covered_function(&info,
-                          utf8_string,
-                          line_number,
-                          hit_count);
-
-    g_array_append_val(array, info);
-
-    return true;
-}
-
-static GArray *
-get_functions_for(JSContext        *context,
-                  JS::HandleObject  coverage_statistics,
-                  JS::HandleValue   filename_value)
-{
-    GArray *array = NULL;
-    JS::RootedValue rval(context);
-    JS::AutoValueArray<1> args(context);
-    args[0].set(filename_value);
-
-    if (!JS_CallFunctionName(context, coverage_statistics, "getFunctionsFor",
-                             args, &rval)) {
-        gjs_log_exception(context);
-        return NULL;
-    }
-
-    if (!get_array_from_js_value(context, rval, sizeof(GjsCoverageFunction),
-        clear_coverage_function, convert_and_insert_function_decl, &array)) {
-        gjs_log_exception(context);
-        return NULL;
-    }
-
-    return array;
-}
-
-static void
-init_covered_branch(GjsCoverageBranch *branch,
-                    unsigned int       point,
-                    bool               was_hit,
-                    GArray            *exits)
-{
-    branch->point = point;
-    branch->hit = !!was_hit;
-    branch->exits = exits;
-}
-
-static void
-clear_coverage_branch(gpointer branch_location)
-{
-    GjsCoverageBranch *branch = (GjsCoverageBranch *) branch_location;
-    g_array_unref(branch->exits);
-}
-
-static bool
-convert_and_insert_branch_exit(GArray         *array,
-                               JSContext      *context,
-                               JS::HandleValue element)
-{
-    if (!element.isObject()) {
-        gjs_throw(context, "Branch exit array element is not an object");
-        return false;
-    }
-
-    JS::RootedObject object(context, &element.toObject());
-
-    int32_t hit_count;
-    int32_t line;
-    if (!get_hit_count_and_line_data(context, object, "branch exit array element",
-                                     &hit_count, &line))
-        return false;
-
-    GjsCoverageBranchExit exit = {
-        (unsigned int) line,
-        (unsigned int) hit_count
-    };
-
-    g_array_append_val(array, exit);
-
-    return true;
-}
-
-static bool
-convert_and_insert_branch_info(GArray         *array,
-                               JSContext      *context,
-                               JS::HandleValue element)
-{
-    if (!element.isObject() && !element.isUndefined()) {
-        gjs_throw(context, "Branch array element is not an object or undefined");
-        return false;
-    }
-
-    if (element.isObject()) {
-        JS::RootedObject object(context, &element.toObject());
-
-        int32_t branch_point;
-        JS::RootedId point_name(context, gjs_intern_string_to_id(context, "point"));
-
-        if (!gjs_object_require_property(context, object,
-                                         "branch array element",
-                                         point_name, &branch_point))
-            return false;
-
-        bool was_hit;
-        JS::RootedId hit_name(context, gjs_intern_string_to_id(context, "hit"));
-
-        if (!gjs_object_require_property(context, object,
-                                         "branch array element",
-                                         hit_name, &was_hit))
-            return false;
-
-        JS::RootedValue branch_exits_value(context);
-        GArray *branch_exits_array = NULL;
-
-        if (!JS_GetProperty(context, object, "exits", &branch_exits_value) ||
-            !branch_exits_value.isObject()) {
-            gjs_throw(context, "Failed to get exits property from element");
-            return false;
-        }
-
-        if (!get_array_from_js_value(context,
-                                     branch_exits_value,
-                                     sizeof(GjsCoverageBranchExit),
-                                     NULL,
-                                     convert_and_insert_branch_exit,
-                                     &branch_exits_array)) {
-            /* Already logged the exception, no need to do anything here */
-            return false;
-        }
-
-        GjsCoverageBranch branch;
-        init_covered_branch(&branch,
-                            branch_point,
-                            was_hit,
-                            branch_exits_array);
-
-        g_array_append_val(array, branch);
-    }
-
-    return true;
-}
-
-static GArray *
-get_branches_for(JSContext        *context,
-                 JS::HandleObject  coverage_statistics,
-                 JS::HandleValue   filename_value)
-{
-    GArray *array = NULL;
-    JS::AutoValueArray<1> args(context);
-    args[0].set(filename_value);
-    JS::RootedValue rval(context);
-
-    if (!JS_CallFunctionName(context, coverage_statistics, "getBranchesFor",
-                             args, &rval)) {
-        gjs_log_exception(context);
-        return NULL;
-    }
-
-    if (!get_array_from_js_value(context, rval, sizeof(GjsCoverageBranch),
-                                 clear_coverage_branch,
-                                 convert_and_insert_branch_info, &array)) {
-        gjs_log_exception(context);
-        return NULL;
-    }
-
-    return array;
-}
-
-typedef struct _GjsCoverageFileStatistics {
-    char       *filename;
-    GArray     *lines;
-    GArray     *functions;
-    GArray     *branches;
-} GjsCoverageFileStatistics;
-
-static bool
-fetch_coverage_file_statistics_from_js(JSContext                 *context,
-                                       JS::HandleObject           coverage_statistics,
-                                       const char                *filename,
-                                       GjsCoverageFileStatistics *statistics)
-{
-    JSAutoCompartment compartment(context, coverage_statistics);
-    JSAutoRequest ar(context);
-
-    JSString *filename_jsstr = JS_NewStringCopyZ(context, filename);
-    JS::RootedValue filename_jsval(context, JS::StringValue(filename_jsstr));
-
-    GArray *lines = get_executed_lines_for(context, coverage_statistics, filename_jsval);
-    GArray *functions = get_functions_for(context, coverage_statistics, filename_jsval);
-    GArray *branches = get_branches_for(context, coverage_statistics, filename_jsval);
-
-    if (!lines || !functions || !branches)
-    {
-        g_clear_pointer(&lines, g_array_unref);
-        g_clear_pointer(&functions, g_array_unref);
-        g_clear_pointer(&branches, g_array_unref);
-        return false;
-    }
-
-    statistics->filename = g_strdup(filename);
-    statistics->lines = lines;
-    statistics->functions = functions;
-    statistics->branches = branches;
-
-    return true;
-}
-
-static void
-gjs_coverage_statistics_file_statistics_clear(gpointer data)
-{
-    GjsCoverageFileStatistics *statistics = (GjsCoverageFileStatistics *) data;
-    g_free(statistics->filename);
-    g_array_unref(statistics->lines);
-    g_array_unref(statistics->functions);
-    g_array_unref(statistics->branches);
-}
-
-static void
-print_statistics_for_file(GjsCoverageFileStatistics *file_statistics,
-                          GFile                     *output_dir,
-                          GOutputStream             *ostream)
-{
-    /* The source file could be a resource, so we must use
-     * g_file_new_for_commandline_arg() to disambiguate between URIs and
-     * filesystem paths. */
-    GFile *source = g_file_new_for_commandline_arg(file_statistics->filename);
-
-    char *diverged_paths = find_diverging_child_components(source, output_dir);
-    GFile *dest = g_file_resolve_relative_path(output_dir, diverged_paths);
-
-    copy_source_file_to_coverage_output(source, dest);
-    g_object_unref(source);
-
-    write_source_file_header(ostream, dest);
-    g_object_unref(dest);
-
-    write_functions(ostream, file_statistics->functions);
-
-    unsigned int functions_hit_count = 0;
-    unsigned int functions_found_count = 0;
-
-    write_functions_hit_counts(ostream,
-                               file_statistics->functions,
-                               &functions_found_count,
-                               &functions_hit_count);
-    write_function_coverage(ostream,
-                            functions_found_count,
-                            functions_hit_count);
-
-    unsigned int branches_hit_count = 0;
-    unsigned int branches_found_count = 0;
-
-    write_branch_coverage(ostream,
-                          file_statistics->branches,
-                          &branches_found_count,
-                          &branches_hit_count);
-    write_branch_totals(ostream,
-                        branches_found_count,
-                        branches_hit_count);
-
-    unsigned int lines_hit_count = 0;
-    unsigned int executable_lines_count = 0;
-
-    write_line_coverage(ostream,
-                        file_statistics->lines,
-                        &lines_hit_count,
-                        &executable_lines_count);
-    write_line_totals(ostream,
-                      lines_hit_count,
-                      executable_lines_count);
-    write_end_of_record(ostream);
-
-    g_free(diverged_paths);
-}
-
-static char **
-get_covered_files(GjsCoverage *coverage)
+write_line(GOutputStream *out,
+           const char    *line)
 {
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    JSContext *context = (JSContext *) gjs_context_get_native_context(priv->context);
-    JSAutoRequest ar(context);
-    JSAutoCompartment ac(context, priv->coverage_statistics);
-    JS::RootedObject rooted_priv(context, priv->coverage_statistics);
-    JS::RootedValue rval(context);
-
-    char **files = NULL;
-    uint32_t n_files;
-
-    if (!JS_CallFunctionName(context, rooted_priv, "getCoveredFiles",
-                             JS::HandleValueArray::empty(), &rval)) {
-        gjs_log_exception(context);
-        return NULL;
-    }
-
-    if (!rval.isObject())
-        return NULL;
-
-    JS::RootedObject files_obj(context, &rval.toObject());
-    if (!JS_GetArrayLength(context, files_obj, &n_files))
-        return NULL;
-
-    files = g_new0(char *, n_files + 1);
-    JS::RootedValue element(context);
-    for (uint32_t i = 0; i < n_files; i++) {
-        GjsAutoJSChar file(context);
-        if (!JS_GetElement(context, files_obj, i, &element))
-            goto error;
-
-        if (!gjs_string_to_utf8(context, element, &file))
-            goto error;
-
-        files[i] = file.copy();
-    }
-
-    files[n_files] = NULL;
-    return files;
-
- error:
-    g_strfreev(files);
-    return NULL;
-}
-
-bool
-gjs_get_file_mtime(GFile    *file,
-                   GTimeVal *mtime)
-{
-    GError *error = NULL;
-    GFileInfo *info = g_file_query_info(file,
-                                        "time::modified,time::modified-usec",
-                                        G_FILE_QUERY_INFO_NONE,
-                                        NULL,
-                                        &error);
-
-    if (!info) {
-        char *path = get_file_identifier(file);
-        g_warning("Failed to get modification time of %s, "
-                  "falling back to checksum method for caching. Reason was: %s",
-                  path, error->message);
-        g_clear_object(&info);
+    GError *error = nullptr;
+    if (!g_output_stream_printf(out, nullptr, nullptr, &error, "%s\n", line)) {
+        g_critical("Error writing coverage data: %s", error->message);
+        g_error_free(error);
         return false;
     }
-
-    g_file_info_get_modification_time(info, mtime);
-    g_clear_object(&info);
-
-    /* For some URI types, eg, resources, the operation getting
-     * the mtime might succeed, but by default zero is returned.
-     *
-     * Check if that is the case for both tv_sec and tv_usec and if
-     * so return false. */
-    return !(mtime->tv_sec == 0 && mtime->tv_usec == 0);
-}
-
-static GBytes *
-read_all_bytes_from_file(GFile *file)
-{
-    /* We have to use g_file_query_exists here since
-     * g_file_test(path, G_FILE_TEST_EXISTS) is implemented in terms
-     * of access(), which doesn't work with resource paths. */
-    if (!g_file_query_exists(file, NULL))
-        return NULL;
-
-    gsize len = 0;
-    gchar *data = NULL;
-
-    GError *error = NULL;
-
-    if (!g_file_load_contents(file,
-                              NULL,
-                              &data,
-                              &len,
-                              NULL,
-                              &error)) {
-        char *path = get_file_identifier(file);
-        g_critical("Unable to read bytes from: %s, reason was: %s\n",
-                   path, error->message);
-        g_clear_error(&error);
-        g_free(path);
-        return NULL;
-    }
-
-    return g_bytes_new_take(data, len);
-}
-
-gchar *
-gjs_get_file_checksum(GFile *file)
-{
-    GBytes *data = read_all_bytes_from_file(file);
-
-    if (!data)
-        return NULL;
-
-    gchar *checksum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA512, data);
-
-    g_bytes_unref(data);
-    return checksum;
-}
-
-/* The binary data for the cache has the following structure:
- *
- * {
- *     array [ tuple {
- *         string filename;
- *         string? checksum;
- *         tuple? {
- *             mtime_sec;
- *             mtime_usec;
- *         }
- *         array [
- *             int line;
- *         ] executable lines;
- *         array [ tuple {
- *             int branch_point;
- *             array [
- *                 int line;
- *             ] exits;
- *         } branch_info ] branches;
- *         array [ tuple {
- *             int line;
- *             string key;
- *         } function ] functions;
- *     } file ] files;
- */
-const char *COVERAGE_STATISTICS_CACHE_BINARY_DATA_TYPE = "a(sm(xx)msaia(iai)a(is))";
-
-GBytes *
-gjs_serialize_statistics(GjsCoverage *coverage)
-{
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    JSContext *js_context = (JSContext *) gjs_context_get_native_context(priv->context);
-
-    JSAutoRequest ar(js_context);
-    JSAutoCompartment ac(js_context, priv->coverage_statistics);
-    JS::RootedObject rooted_priv(js_context, priv->coverage_statistics);
-    JS::RootedValue string_value_return(js_context);
-
-    if (!JS_CallFunctionName(js_context, rooted_priv, "stringify",
-                             JS::HandleValueArray::empty(),
-                             &string_value_return)) {
-        gjs_log_exception(js_context);
-        return NULL;
-    }
-
-    if (!string_value_return.isString())
-        return NULL;
-
-    /* Free'd by g_bytes_new_take */
-    GjsAutoJSChar statistics_as_json_string(js_context);
-
-    if (!gjs_string_to_utf8(js_context,
-                            string_value_return.get(),
-                            &statistics_as_json_string)) {
-        gjs_log_exception(js_context);
-        return NULL;
-    }
-
-    int json_string_len = strlen(statistics_as_json_string);
-    auto json_bytes =
-        reinterpret_cast<uint8_t*>(statistics_as_json_string.copy());
-
-    return g_bytes_new_take(json_bytes,
-                            json_string_len);
-}
-
-static JSString *
-gjs_deserialize_cache_to_object_for_compartment(JSContext        *context,
-                                                JS::HandleObject global_object,
-                                                GBytes           *cache_data)
-{
-    JSAutoRequest ar(context);
-    JSAutoCompartment ac(context,
-                         JS_GetGlobalForObject(context,
-                                               global_object));
-
-    gsize len = 0;
-    auto string = static_cast<const char *>(g_bytes_get_data(cache_data, &len));
-
-    return JS_NewStringCopyN(context, string, len);
-}
-
-JSString *
-gjs_deserialize_cache_to_object(GjsCoverage *coverage,
-                                GBytes      *cache_data)
-{
-    /* Deserialize into an object with the following structure:
-     *
-     * object = {
-     *     'filename': {
-     *         contents: (file contents),
-     *         nLines: (number of lines in file),
-     *         lines: Number[nLines + 1],
-     *         branches: Array for n_branches of {
-     *             point: branch_point,
-     *             exits: Number[nLines + 1]
-     *         },
-     *         functions: Array for n_functions of {
-     *             key: function_name,r
-     *             line: line
-     *         }
-     * }
-     */
-
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    JSContext *context = (JSContext *) gjs_context_get_native_context(priv->context);
-    JSAutoRequest ar(context);
-    JSAutoCompartment ac(context, priv->coverage_statistics);
-    JS::RootedObject global_object(context,
-                                   JS_GetGlobalForObject(context, priv->coverage_statistics));
-    return gjs_deserialize_cache_to_object_for_compartment(context, global_object, cache_data);
-}
-
-static GArray *
-gjs_fetch_statistics_from_js(GjsCoverage *coverage,
-                             gchar       **coverage_files)
-{
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    JSContext          *js_context = (JSContext *) gjs_context_get_native_context(priv->context);
-
-    GArray *file_statistics_array = g_array_new(false,
-                                                false,
-                                                sizeof(GjsCoverageFileStatistics));
-    g_array_set_clear_func(file_statistics_array,
-                           gjs_coverage_statistics_file_statistics_clear);
-
-    JS::RootedObject rooted_coverage_statistics(js_context,
-                                                priv->coverage_statistics);
-
-    char **file_iter = coverage_files;
-    while (*file_iter) {
-        GjsCoverageFileStatistics statistics;
-        if (fetch_coverage_file_statistics_from_js(js_context,
-                                                   rooted_coverage_statistics,
-                                                   *file_iter,
-                                                   &statistics))
-            g_array_append_val(file_statistics_array, statistics);
-        else
-            g_warning("Couldn't fetch statistics for %s", *file_iter);
-
-        ++file_iter;
-    }
-
-    return file_statistics_array;
-}
-
-bool
-gjs_write_cache_file(GFile  *file,
-                     GBytes *cache)
-{
-    gsize cache_len = 0;
-    char *cache_data = (char *) g_bytes_get_data(cache, &cache_len);
-    GError *error = NULL;
-
-    if (!g_file_replace_contents(file,
-                                 cache_data,
-                                 cache_len,
-                                 NULL,
-                                 false,
-                                 G_FILE_CREATE_NONE,
-                                 NULL,
-                                 NULL,
-                                 &error)) {
-        char *path = get_file_identifier(file);
-        g_warning("Failed to write all bytes to %s, reason was: %s\n",
-                  path, error->message);
-        g_warning("Will remove this file to prevent inconsistent cache "
-                  "reads next time.");
-        g_clear_error(&error);
-        if (!g_file_delete(file, NULL, &error)) {
-            g_assert(error != NULL);
-            g_critical("Deleting %s failed because %s! You will need to "
-                       "delete it manually before running the coverage "
-                       "mode again.", path, error->message);
-            g_clear_error(&error);
-        }
-        g_free(path);
-
-        return false;
-    }
-
     return true;
 }
 
-static bool
-coverage_statistics_has_stale_cache(GjsCoverage *coverage)
-{
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    JSContext          *js_context = (JSContext *) gjs_context_get_native_context(priv->context);
-
-    JSAutoRequest ar(js_context);
-    JSAutoCompartment ac(js_context, priv->coverage_statistics);
-    JS::RootedObject rooted_priv(js_context, priv->coverage_statistics);
-    JS::RootedValue stale_cache_value(js_context);
-    if (!JS_CallFunctionName(js_context, rooted_priv, "staleCache",
-                             JS::HandleValueArray::empty(),
-                             &stale_cache_value)) {
-        gjs_log_exception(js_context);
-        g_error("Failed to call into javascript to get stale cache value. This is a bug");
-    }
-
-    return stale_cache_value.toBoolean();
-}
-
-static unsigned int _suppressed_coverage_messages_count = 0;
-
 /**
  * gjs_coverage_write_statistics:
  * @coverage: A #GjsCoverage
@@ -1192,11 +227,14 @@ static unsigned int _suppressed_coverage_messages_count = 0;
 void
 gjs_coverage_write_statistics(GjsCoverage *coverage)
 {
+    using AutoCChar = std::unique_ptr<char, decltype(&free)>;
+    using AutoStrv = std::unique_ptr<char *, decltype(&g_strfreev)>;
+
     GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
     GError *error = NULL;
 
     JSContext *context = (JSContext *) gjs_context_get_native_context(priv->context);
-    JSAutoCompartment compartment(context, priv->coverage_statistics);
+    JSAutoCompartment ac(context, gjs_get_import_global(context));
     JSAutoRequest ar(context);
 
     /* Create output directory if it doesn't exist */
@@ -1211,53 +249,70 @@ gjs_coverage_write_statistics(GjsCoverage *coverage)
 
     GFile *output_file = g_file_get_child(priv->output_dir, "coverage.lcov");
 
+    size_t lcov_length;
+    AutoCChar lcov(js::GetCodeCoverageSummary(context, &lcov_length), free);
+
     GOutputStream *ostream =
         G_OUTPUT_STREAM(g_file_append_to(output_file,
                                          G_FILE_CREATE_NONE,
                                          NULL,
                                          &error));
+    if (!ostream) {
+        g_critical("Could not write coverage data: %s", error->message);
+        g_clear_error(&error);
+        return;
+    }
 
-    char **executed_coverage_files = get_covered_files(coverage);
-    GArray *file_statistics_array = gjs_fetch_statistics_from_js(coverage,
-                                                                 executed_coverage_files);
-
-    for (size_t i = 0; i < file_statistics_array->len; ++i)
-    {
-        GjsCoverageFileStatistics *statistics = &(g_array_index(file_statistics_array, 
GjsCoverageFileStatistics, i));
+    AutoStrv lcov_lines(g_strsplit(lcov.get(), "\n", -1), g_strfreev);
+    GjsAutoChar test_name;
+    bool ignoring_file = false;
 
-        /* Only print statistics if the file was actually executed */
-        for (char **iter = executed_coverage_files; *iter; ++iter) {
-            if (g_strcmp0(*iter, statistics->filename) == 0) {
-                print_statistics_for_file(statistics, priv->output_dir, ostream);
+    for (const char * const *iter = lcov_lines.get(); *iter; iter++) {
+        if (ignoring_file) {
+            if (strcmp(*iter, "end_of_record") == 0)
+                ignoring_file = false;
+            continue;
+        }
 
-                /* Inner loop */
-                break;
+        if (g_str_has_prefix(*iter, "TN:")) {
+            /* Don't write the test name if the next line shows we are
+             * ignoring the source file */
+            test_name = *iter;
+            continue;
+        } else if (g_str_has_prefix(*iter, "SF:")) {
+            const char *filename = *iter + 3;
+            if (!filename_has_coverage_prefixes(coverage, filename)) {
+                ignoring_file = true;
+                continue;
             }
-        }
-    }
 
-    g_strfreev(executed_coverage_files);
+            /* Now we can write the test name before writing the source file */
+            if (!write_line(ostream, test_name.get()))
+                break;
 
-    const bool has_cache_path = priv->cache != NULL;
-    const bool cache_is_stale = coverage_statistics_has_stale_cache(coverage);
+            /* The source file could be a resource, so we must use
+             * g_file_new_for_commandline_arg() to disambiguate between URIs and
+             * filesystem paths. */
+            GjsAutoUnref<GFile> source_file = g_file_new_for_commandline_arg(filename);
+            GjsAutoChar diverged_paths =
+                find_diverging_child_components(source_file, priv->output_dir);
+            GjsAutoUnref<GFile> destination_file =
+                g_file_resolve_relative_path(priv->output_dir, diverged_paths);
+            copy_source_file_to_coverage_output(source_file, destination_file);
+
+            /* Rewrite the source file path to be relative to the output
+             * dir so that genhtml will find it */
+            write_source_file_header(ostream, destination_file);
+            continue;
+        }
 
-    if (has_cache_path && cache_is_stale) {
-        GBytes *cache_data = gjs_serialize_statistics(coverage);
-        gjs_write_cache_file(priv->cache, cache_data);
-        g_bytes_unref(cache_data);
+        if (!write_line(ostream, *iter))
+            break;
     }
 
-    char *output_file_path = g_file_get_path(priv->output_dir);
-    g_message("Wrote coverage statistics to %s", output_file_path);
-    if (_suppressed_coverage_messages_count) {
-        g_message("There were %i suppressed message(s) when collecting "
-                  "coverage, set GJS_SHOW_COVERAGE_MESSAGES to see them.",
-                  _suppressed_coverage_messages_count);
-        _suppressed_coverage_messages_count = 0;
-    }
+    GjsAutoChar output_file_path = g_file_get_path(output_file);
+    g_message("Wrote coverage statistics to %s", output_file_path.get());
 
-    g_free(output_file_path);
-    g_array_unref(file_statistics_array);
     g_object_unref(ostream);
     g_object_unref(output_file);
 }
@@ -1267,234 +322,19 @@ gjs_coverage_init(GjsCoverage *self)
 {
 }
 
-static bool
-coverage_log(JSContext *context,
-             unsigned   argc,
-             JS::Value *vp)
-{
-    JS::CallArgs argv = JS::CallArgsFromVp (argc, vp);
-    GjsAutoJSChar s(context);
-    JSExceptionState *exc_state;
-
-    if (argc != 1) {
-        gjs_throw(context, "Must pass a single argument to log()");
-        return false;
-    }
-
-    JSAutoRequest ar(context);
-
-    if (!g_getenv("GJS_SHOW_COVERAGE_MESSAGES")) {
-        _suppressed_coverage_messages_count++;
-        argv.rval().setUndefined();
-        return true;
-    }
-
-    /* JS::ToString might throw, in which case we will only log that the value
-     * could not be converted to string */
-    exc_state = JS_SaveExceptionState(context);
-    JS::RootedString jstr(context, JS::ToString(context, argv[0]));
-    if (jstr)
-        argv[0].setString(jstr);  // GC root
-    JS_RestoreExceptionState(context, exc_state);
-
-    if (!jstr) {
-        g_message("JS LOG: <cannot convert value to string>");
-        return true;
-    }
-
-    if (!gjs_string_to_utf8(context, JS::StringValue(jstr), &s)) {
-        return false;
-    }
-
-    g_message("JS COVERAGE MESSAGE: %s", s.get());
-
-    argv.rval().setUndefined();
-    return true;
-}
-
-static GFile *
-get_file_from_call_args_filename(JSContext    *context,
-                                 JS::CallArgs &args) {
-    GjsAutoJSChar filename(context);
-
-    if (!gjs_parse_call_args(context, "getFileContents", args, "s",
-                             "filename", &filename))
-        return NULL;
-
-    /* path could be a resource, so use g_file_new_for_commandline_arg. */
-    return g_file_new_for_commandline_arg(filename);
-}
-
-static bool
-coverage_get_file_modification_time(JSContext *context,
-                                    unsigned  argc,
-                                    JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    GTimeVal mtime;
-    bool ret = false;
-    GFile *file = get_file_from_call_args_filename(context, args);
-
-    if (!file)
-        return false;
-
-    if (gjs_get_file_mtime(file, &mtime)) {
-        JS::AutoValueArray<2> mtime_values_array(context);
-        mtime_values_array[0].setInt32(mtime.tv_sec);
-        mtime_values_array[1].setInt32(mtime.tv_usec);
-        JS::RootedObject array_obj(context,
-            JS_NewArrayObject(context, mtime_values_array));
-        if (!array_obj)
-            goto out;
-        args.rval().setObject(*array_obj);
-    } else {
-        args.rval().setNull();
-    }
-
-    ret = true;
-
-out:
-    g_object_unref(file);
-    return ret;
-}
-
-static bool
-coverage_get_file_checksum(JSContext *context,
-                           unsigned  argc,
-                           JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    GFile *file = get_file_from_call_args_filename(context, args);
-
-    if (!file)
-        return false;
-
-    char *checksum = gjs_get_file_checksum(file);
-
-    if (!checksum) {
-        char *filename = get_file_identifier(file);
-        gjs_throw(context, "Failed to read %s and get its checksum", filename);
-        g_free(filename);
-        g_object_unref(file);
-        return false;
-    }
-
-    args.rval().setString(JS_NewStringCopyZ(context, checksum));
-
-    g_object_unref(file);
-    g_free(checksum);
-    return true;
-}
-
-static bool
-coverage_get_file_contents(JSContext *context,
-                           unsigned   argc,
-                           JS::Value *vp)
-{
-    bool ret = false;
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    GFile *file = NULL;
-    char *script = NULL;
-    gsize script_len;
-    GError *error = NULL;
-
-    file = get_file_from_call_args_filename(context, args);
-    if (!file)
-        return false;
-
-    if (!g_file_load_contents(file,
-                              NULL,
-                              &script,
-                              &script_len,
-                              NULL,
-                              &error)) {
-        char *filename = get_file_identifier(file);
-        gjs_throw(context, "Failed to load contents for filename %s: %s", filename, error->message);
-        g_free(filename);
-        goto out;
-    }
-
-    args.rval().setString(JS_NewStringCopyN(context, script, script_len));
-    ret = true;
-
- out:
-    g_clear_error(&error);
-    g_object_unref(file);
-    g_free(script);
-    return ret;
-}
-
-static JSFunctionSpec coverage_funcs[] = {
-    JS_FS("log", coverage_log, 1, GJS_MODULE_PROP_FLAGS),
-    JS_FS("getFileContents", coverage_get_file_contents, 1, GJS_MODULE_PROP_FLAGS),
-    JS_FS("getFileModificationTime", coverage_get_file_modification_time, 1, GJS_MODULE_PROP_FLAGS),
-    JS_FS("getFileChecksum", coverage_get_file_checksum, 1, GJS_MODULE_PROP_FLAGS),
-    JS_FS_END
-};
-
 static void
-coverage_statistics_tracer(JSTracer *trc, void *data)
+coverage_tracer(JSTracer *trc, void *data)
 {
     GjsCoverage *coverage = (GjsCoverage *) data;
     GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
 
-    JS::TraceEdge<JSObject *>(trc, &priv->coverage_statistics,
-                              "coverage_statistics");
-}
-
-/* This function is mainly used in the tests in order to fiddle with
- * the internals of the coverage statisics collector on the coverage
- * compartment side */
-bool
-gjs_run_script_in_coverage_compartment(GjsCoverage *coverage,
-                                       const char  *script)
-{
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    JSContext          *js_context = (JSContext *) gjs_context_get_native_context(priv->context);
-    JSAutoCompartment ac(js_context, priv->coverage_statistics);
-    JSAutoRequest ar(js_context);
-
-    JS::CompileOptions options(js_context);
-    options.setUTF8(true);
-
-    JS::RootedValue rval(js_context);
-    if (!JS::Evaluate(js_context, options, script, strlen(script), &rval)) {
-        gjs_log_exception(js_context);
-        g_warning("Failed to evaluate <coverage_modifier>");
-        return false;
-    }
-
-    return true;
-}
-
-bool
-gjs_inject_value_into_coverage_compartment(GjsCoverage     *coverage,
-                                           JS::HandleValue  value,
-                                           const char      *property)
-{
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-
-    JSContext     *js_context = (JSContext *) gjs_context_get_native_context(priv->context);
-    JSAutoRequest ar(js_context);
-    JSAutoCompartment ac(js_context, priv->coverage_statistics);
-
-    JS::RootedObject coverage_global_scope(js_context,
-                                           JS_GetGlobalForObject(js_context, priv->coverage_statistics));
-
-    if (!JS_SetProperty(js_context, coverage_global_scope, property,
-                        value)) {
-        g_warning("Failed to set property %s to requested value", property);
-        return false;
-    }
-
-    return true;
+    JS::TraceEdge<JSObject *>(trc, &priv->compartment, "Coverage compartment");
 }
 
 static bool
 bootstrap_coverage(GjsCoverage *coverage)
 {
     GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    GBytes             *cache_bytes = NULL;
 
     JSContext *context = (JSContext *) gjs_context_get_native_context(priv->context);
     JSAutoRequest ar(context);
@@ -1513,58 +353,14 @@ bootstrap_coverage(GjsCoverage *coverage)
         JS::RootedValue debuggeeWrapperValue(context, JS::ObjectValue(*debuggeeWrapper));
         if (!JS_SetProperty(context, debugger_compartment, "debuggee",
                             debuggeeWrapperValue) ||
-            !JS_DefineFunctions(context, debugger_compartment, coverage_funcs) ||
             !gjs_define_global_properties(context, debugger_compartment,
                                           "coverage"))
             return false;
 
-        JS::RootedObject coverage_statistics_constructor(context);
-        JS::RootedId coverage_statistics_name(context,
-            gjs_intern_string_to_id(context, "CoverageStatistics"));
-        if (!gjs_object_require_property(context, debugger_compartment,
-                                         "debugger compartment",
-                                         coverage_statistics_name,
-                                         &coverage_statistics_constructor))
-            return false;
-
-        /* Create value for holding the cache. This will be undefined if
-         * the cache does not exist, otherwise it will be an object set
-         * to the value of the cache */
-        JS::RootedValue cache_value(context);
-
-        if (priv->cache)
-            cache_bytes = read_all_bytes_from_file(priv->cache);
-
-        if (cache_bytes) {
-            JSString *cache_object = gjs_deserialize_cache_to_object_for_compartment(context,
-                                                                                     debugger_compartment,
-                                                                                     cache_bytes);
-            cache_value.setString(cache_object);
-            g_bytes_unref(cache_bytes);
-        }
-
-        /* Now create the array to pass the desired prefixes over */
-        JSObject *prefixes = gjs_build_string_array(context, -1, priv->prefixes);
-
-        JS::AutoValueArray<3> coverage_statistics_constructor_args(context);
-        coverage_statistics_constructor_args[0].setObject(*prefixes);
-        coverage_statistics_constructor_args[1].set(cache_value);
-        coverage_statistics_constructor_args[2]
-            .setBoolean(g_getenv("GJS_DEBUG_COVERAGE_EXECUTED_LINES"));
-
-        JSObject *coverage_statistics = JS_New(context,
-                                               coverage_statistics_constructor,
-                                               coverage_statistics_constructor_args);
-
-        if (!coverage_statistics) {
-            gjs_throw(context, "Failed to create coverage_statistics object");
-            return false;
-        }
-
         /* Add a tracer, as suggested by jdm on #jsapi */
-        JS_AddExtraGCRootsTracer(context, coverage_statistics_tracer, coverage);
+        JS_AddExtraGCRootsTracer(context, coverage_tracer, coverage);
 
-        priv->coverage_statistics = coverage_statistics;
+        priv->compartment = debugger_compartment;
     }
 
     return true;
@@ -1577,12 +373,7 @@ gjs_coverage_constructed(GObject *object)
 
     GjsCoverage *coverage = GJS_COVERAGE(object);
     GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-    new (&priv->coverage_statistics) JS::Heap<JSObject *>();
-
-    if (!priv->cache_specified) {
-        g_message("Cache path was not given, picking default one");
-        priv->cache = g_file_new_for_path(".internal-gjs-coverage-cache");
-    }
+    new (&priv->compartment) JS::Heap<JSObject *>();
 
     if (!bootstrap_coverage(coverage)) {
         JSContext *context = static_cast<JSContext *>(gjs_context_get_native_context(priv->context));
@@ -1608,9 +399,6 @@ gjs_coverage_set_property(GObject      *object,
         priv->context = GJS_CONTEXT(g_value_dup_object(value));
         break;
     case PROP_CACHE:
-        priv->cache_specified = true;
-        /* g_value_dup_object() adds a reference if not NULL */
-        priv->cache = static_cast<GFile *>(g_value_dup_object(value));
         break;
     case PROP_OUTPUT_DIRECTORY:
         priv->output_dir = G_FILE(g_value_dup_object(value));
@@ -1622,33 +410,6 @@ gjs_coverage_set_property(GObject      *object,
 }
 
 static void
-gjs_clear_js_side_statistics_from_coverage_object(GjsCoverage *coverage)
-{
-    GjsCoveragePrivate *priv = (GjsCoveragePrivate *) gjs_coverage_get_instance_private(coverage);
-
-    if (priv->coverage_statistics) {
-        /* Remove tracer before disposing the context */
-        JSContext *js_context = (JSContext *) gjs_context_get_native_context(priv->context);
-        JSAutoRequest ar(js_context);
-        JSAutoCompartment ac(js_context, priv->coverage_statistics);
-        JS::RootedObject rooted_priv(js_context, priv->coverage_statistics);
-        JS::RootedValue rval(js_context);
-        if (!JS_CallFunctionName(js_context, rooted_priv, "deactivate",
-                                 JS::HandleValueArray::empty(), &rval)) {
-            gjs_log_exception(js_context);
-            g_error("Failed to deactivate debugger - this is a fatal error");
-        }
-
-        /* Remove GC roots trace after we've decomissioned the object
-         * and no longer need it to be traced here. */
-        JS_RemoveExtraGCRootsTracer(js_context, coverage_statistics_tracer,
-                                    coverage);
-
-        priv->coverage_statistics = NULL;
-    }
-}
-
-static void
 gjs_coverage_dispose(GObject *object)
 {
     GjsCoverage *coverage = GJS_COVERAGE(object);
@@ -1656,7 +417,10 @@ gjs_coverage_dispose(GObject *object)
 
     /* Decomission objects inside of the JSContext before
      * disposing of the context */
-    gjs_clear_js_side_statistics_from_coverage_object(coverage);
+    auto cx = static_cast<JSContext *>(gjs_context_get_native_context(priv->context));
+    JS_RemoveExtraGCRootsTracer(cx, coverage_tracer, coverage);
+    priv->compartment = nullptr;
+
     g_clear_object(&priv->context);
 
     G_OBJECT_CLASS(gjs_coverage_parent_class)->dispose(object);
@@ -1670,8 +434,7 @@ gjs_coverage_finalize (GObject *object)
 
     g_strfreev(priv->prefixes);
     g_clear_object(&priv->output_dir);
-    g_clear_object(&priv->cache);
-    priv->coverage_statistics.~Heap();
+    priv->compartment.~Heap();
 
     G_OBJECT_CLASS(gjs_coverage_parent_class)->finalize(object);
 }
@@ -1697,10 +460,10 @@ gjs_coverage_class_init (GjsCoverageClass *klass)
                                                    GJS_TYPE_CONTEXT,
                                                    (GParamFlags) (G_PARAM_CONSTRUCT_ONLY | 
G_PARAM_WRITABLE));
     properties[PROP_CACHE] = g_param_spec_object("cache",
-                                                 "Cache",
-                                                 "File containing a cache to preload ASTs from",
+                                                 "Deprecated property",
+                                                 "Has no effect",
                                                  G_TYPE_FILE,
-                                                 (GParamFlags) (G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+                                                 (GParamFlags) (G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | 
G_PARAM_DEPRECATED));
     properties[PROP_OUTPUT_DIRECTORY] =
         g_param_spec_object("output-directory", "Output directory",
                             "Directory handle at which to output coverage statistics",
@@ -1720,9 +483,8 @@ gjs_coverage_class_init (GjsCoverageClass *klass)
  * @output_dir: A #GFile handle to a directory in which to write coverage
  * information
  *
- * Creates a new #GjsCoverage object, using a cache in a temporary file to
- * pre-fill the AST information for the specified scripts in @prefixes, so long
- * as the data in the cache has the same mtime as those scripts.
+ * Creates a new #GjsCoverage object that collects coverage information for
+ * any scripts run in @context.
  *
  * Scripts which were provided as part of @prefixes will be written out to
  * @output_dir, in the same directory structure relative to the source dir where
@@ -1744,28 +506,3 @@ gjs_coverage_new (const char * const *prefixes,
 
     return coverage;
 }
-
-GjsCoverage *
-gjs_coverage_new_internal_with_cache(const char * const *coverage_prefixes,
-                                     GjsContext         *context,
-                                     GFile              *output_dir,
-                                     GFile              *cache)
-{
-    GjsCoverage *coverage =
-        GJS_COVERAGE(g_object_new(GJS_TYPE_COVERAGE,
-                                  "prefixes", coverage_prefixes,
-                                  "context", context,
-                                  "cache", cache,
-                                  "output-directory", output_dir,
-                                  NULL));
-
-    return coverage;
-}
-
-GjsCoverage *
-gjs_coverage_new_internal_without_cache(const char * const *prefixes,
-                                        GjsContext         *cx,
-                                        GFile              *output_dir)
-{
-    return gjs_coverage_new_internal_with_cache(prefixes, cx, output_dir, NULL);
-}
diff --git a/modules/_bootstrap/coverage.js b/modules/_bootstrap/coverage.js
index bd17783..b299870 100644
--- a/modules/_bootstrap/coverage.js
+++ b/modules/_bootstrap/coverage.js
@@ -1,1005 +1,6 @@
-/* exported CoverageStatistics */
-/*
- * Copyright (c) 2014 Endless Mobile, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- *
- * Authored By: Sam Spilsbury <sam endlessm com>
- */
+(function(exports) {
+    'use strict';
 
-function getSubNodesForNode(node) {
-    let subNodes = [];
-    switch (node.type) {
-    /* These statements have a single body */
-    case 'LabeledStatement':
-    case 'WithStatement':
-    case 'CatchClause':
-    case 'ClassMethod':
-        subNodes.push(node.body);
-        break;
-    case 'LetStatement':
-        Array.prototype.push.apply(subNodes, node.head);
-        subNodes.push(node.body);
-        break;
-    case 'WhileStatement':
-    case 'DoWhileStatement':
-        subNodes.push(node.body);
-        subNodes.push(node.test);
-        break;
-    case 'ForStatement':
-        if (node.init !== null)
-            subNodes.push(node.init);
-        if (node.test !== null)
-            subNodes.push(node.test);
-        if (node.update !== null)
-            subNodes.push(node.update);
-
-        subNodes.push(node.body);
-        break;
-    case 'ForInStatement':
-        if (node.each)
-            subNodes.push(node.left);
-
-        subNodes.push(node.right, node.body);
-        break;
-    case 'ForOfStatement':
-        subNodes.push(node.left, node.right, node.body);
-        break;
-    case 'BlockStatement':
-    case 'ClassStatement':
-    case 'ClassExpression':
-        subNodes.push(...node.body);
-        break;
-    case 'ThrowStatement':
-    case 'ReturnStatement':
-    case 'YieldExpression':
-        if (node.argument !== null)
-            subNodes.push(node.argument);
-        break;
-    case 'ExpressionStatement':
-        subNodes.push(node.expression);
-        break;
-    case 'AssignmentExpression':
-    case 'BinaryExpression':
-    case 'LogicalExpression':
-        subNodes.push(node.left, node.right);
-        break;
-    case 'ConditionalExpression':
-        subNodes.push(node.test, node.consequent, node.alternate);
-        break;
-    case 'ObjectExpression':
-        node.properties.forEach(function(prop) {
-            subNodes.push(prop.value);
-        });
-        break;
-    case 'ArrayExpression':
-        node.elements.forEach(function(elem) {
-            if (elem !== null)
-                subNodes.push(elem);
-        });
-        break;
-    case 'FunctionDeclaration':
-    case 'FunctionExpression':
-    case 'ArrowFunctionExpression':
-        subNodes.push(...node.defaults);
-        subNodes.push(node.body);
-        break;
-    case 'SequenceExpression':
-        Array.prototype.push.apply(subNodes, node.expressions);
-        break;
-    case 'UnaryExpression':
-    case 'UpdateExpression':
-        subNodes.push(node.argument);
-        break;
-    case 'ComprehensionExpression':
-    case 'GeneratorExpression':
-        subNodes.push(node.body);
-        Array.prototype.push.apply(subNodes, node.blocks);
-        if (node.filter !== null)
-            subNodes.push(node.filter);
-        break;
-    case 'ComprehensionBlock':
-        subNodes.push(node.right);
-        break;
-    case 'ComprehensionIf':
-        subNodes.push(node.test);
-        break;
-    /* It is very possible that there might be something
-     * interesting in the function arguments, so we need to
-     * walk them too */
-    case 'NewExpression':
-    case 'CallExpression':
-        Array.prototype.push.apply(subNodes, node.arguments);
-        subNodes.push(node.callee);
-        break;
-    /* These statements might have multiple different bodies
-     * depending on whether or not they were entered */
-    case 'IfStatement':
-        subNodes = [node.test, node.consequent];
-        if (node.alternate !== null)
-            subNodes.push(node.alternate);
-        break;
-    case 'TryStatement':
-        subNodes = [node.block];
-        if (node.handler !== null)
-            subNodes.push(node.handler);
-        if (node.finalizer !== null)
-            subNodes.push(node.finalizer);
-        break;
-    case 'SwitchStatement':
-        subNodes.push(node.discriminant);
-        for (let caseClause of node.cases) {
-            caseClause.consequent.forEach(function(expression) {
-                subNodes.push(expression);
-            });
-        }
-        break;
-    case 'VariableDeclaration':
-        Array.prototype.push.apply(subNodes, node.declarations);
-        break;
-    case 'VariableDeclarator':
-        if (node.init !== null)
-            subNodes.push(node.init);
-        break;
-    case 'MemberExpression':
-        subNodes.push(node.object);
-        if (node.computed)
-            subNodes.push(node.property);
-
-        break;
-    }
-
-    return subNodes;
-}
-
-function collectForSubNodes(subNodes, collector) {
-    let result = [];
-    if (subNodes !== undefined &&
-        subNodes.length > 0) {
-
-        subNodes.forEach(function(node) {
-            let nodeResult = collector(node);
-            if (nodeResult !== undefined)
-                Array.prototype.push.apply(result, nodeResult);
-
-            let subNodeResults = collectForSubNodes(getSubNodesForNode(node),
-                                                    collector);
-
-            Array.prototype.push.apply(result, subNodeResults);
-        });
-    }
-
-    return result;
-}
-
-function _getFunctionKeyFromReflectedFunction(node) {
-    let name = node.id !== null ? node.id.name : '(anonymous)';
-    let line = node.loc.start.line;
-    let n_params = node.params.length;
-
-    return [name, line, n_params].join(':');
-}
-
-/* Unfortunately, the Reflect API doesn't give us enough information to
- * uniquely identify a function. A function might be anonymous, in which
- * case the JS engine uses some heurisitics to get a unique string identifier
- * but that isn't available to us here.
- *
- * There's also the edge-case where functions with the same name might be
- * defined within the same scope, or multiple anonymous functions might
- * be defined on the same line. In that case, it will look like we entered
- * the same function multiple times since we can't get column information
- * from the engine-side.
- *
- * For instance:
- *
- * 1. function f() {
- *       function f() {
- *       }
- *    }
- *
- * 2. let a = function() { function(a, b) {} };
- *
- * 3. let a = function() { function () {} }
- *
- * We can work-around case 1 by using the line numbers to get a unique identifier.
- * We can work-around case 2 by using the arguments length to get a unique identifier
- * We can't work-around case 3. The best thing we can do is warn that coverage
- * reports might be inaccurate as a result */
-function functionsForNode(node) {
-    let functionNames = [];
-    switch (node.type) {
-    case 'FunctionDeclaration':
-    case 'FunctionExpression':
-    case 'ArrowFunctionExpression':
-        functionNames.push({ key: _getFunctionKeyFromReflectedFunction(node),
-                             line: node.loc.start.line,
-                             n_params: node.params.length });
-    }
-
-    return functionNames;
-}
-
-function functionsForAST(ast) {
-    return collectForSubNodes(ast.body, functionsForNode);
-}
-
-/* If a branch' consequent is a block statement, there's
- * a chance that it could start on the same line, although
- * that's not where execution really starts. If it is
- * a block statement then handle the case and go
- * to the first line where execution starts */
-function getBranchExitStartLine(branchBodyNode) {
-    switch (branchBodyNode.type) {
-    case 'BlockStatement':
-        /* Hit a block statement, but nothing inside, can never
-         * be executed, tell the upper level to move on to the next
-         * statement */
-        if (branchBodyNode.body.length === 0)
-            return -1;
-
-        /* Handle the case where we have nested block statements
-         * that never actually get to executable code by handling
-         * all statements within a block */
-        for (let statement of branchBodyNode.body) {
-            let startLine = getBranchExitStartLine(statement);
-            if (startLine !== -1)
-                return startLine;
-        }
-
-        /* Couldn't find an executable line inside this block */
-        return -1;
-
-    case 'SwitchCase':
-        /* Hit a switch, but nothing inside, can never
-         * be executed, tell the upper level to move on to the next
-         * statement */
-        if (branchBodyNode.consequent.length === 0)
-            return -1;
-
-        /* Handle the case where we have nested block statements
-         * that never actually get to executable code by handling
-         * all statements within a block */
-        for (let statement of branchBodyNode.consequent) {
-            let startLine = getBranchExitStartLine(statement);
-            if (startLine !== -1) {
-                return startLine;
-            }
-        }
-
-        /* Couldn't find an executable line inside this block */
-        return -1;
-    /* These types of statements are never executable */
-    case 'EmptyStatement':
-    case 'LabeledStatement':
-        return -1;
-    default:
-        break;
-    }
-
-    return branchBodyNode.loc.start.line;
-}
-
-function branchesForNode(node) {
-    let branches = [];
-
-    let branchExitNodes = [];
-    switch (node.type) {
-    case 'IfStatement':
-        branchExitNodes.push(node.consequent);
-        if (node.alternate !== null)
-            branchExitNodes.push(node.alternate);
-        break;
-    case 'WhileStatement':
-    case 'DoWhileStatement':
-        branchExitNodes.push(node.body);
-        break;
-    case 'SwitchStatement':
-
-        /* The case clauses by themselves are never executable
-         * so find the actual exits */
-        Array.prototype.push.apply(branchExitNodes, node.cases);
-        break;
-    default:
-        break;
-    }
-
-    let branchExitStartLines = branchExitNodes.map(getBranchExitStartLine);
-    branchExitStartLines = branchExitStartLines.filter(function(line) {
-        return line !== -1;
-    });
-
-    /* Branch must have at least one exit */
-    if (branchExitStartLines.length) {
-        branches.push({ point: node.loc.start.line,
-                        exits: branchExitStartLines });
-    }
-
-    return branches;
-}
-
-function branchesForAST(ast) {
-    return collectForSubNodes(ast.body, branchesForNode);
-}
-
-function expressionLinesForNode(statement) {
-    let expressionLines = [];
-
-    let expressionNodeTypes = ['Expression',
-                               'Declaration',
-                               'Statement',
-                               'Clause',
-                               'Literal',
-                               'Identifier'];
-
-    if (expressionNodeTypes.some(function(type) {
-            return statement.type.indexOf(type) !== -1;
-        })) {
-
-        /* These expressions aren't executable on their own */
-        switch (statement.type) {
-        case 'FunctionDeclaration':
-        case 'Literal':
-            break;
-        /* Perplexingly, an empty block statement is actually executable,
-         * push it if it is */
-        case 'BlockStatement':
-            if (statement.body.length !== 0)
-                break;
-            expressionLines.push(statement.loc.start.line);
-            break;
-        case 'VariableDeclaration':
-            if (statement.kind === 'var') {
-                /* empty 'var foo;' declarations are not executable */
-                let nonEmpty = statement.declarations.filter(decl =>
-                    decl.init !== null);
-                nonEmpty.forEach(decl => {
-                    expressionLines.push(decl.loc.start.line);
-                });
-                break;
-            }
-            /* fall through */
-        default:
-            expressionLines.push(statement.loc.start.line);
-            break;
-        }
-    }
-
-    return expressionLines;
-}
-
-function deduplicate(list) {
-    return list.filter(function(elem, pos, self) {
-        return self.indexOf(elem) === pos;
-    });
-}
-
-function expressionLinesForAST(ast) {
-    let allExpressions = collectForSubNodes(ast.body, expressionLinesForNode);
-    allExpressions = deduplicate(allExpressions);
-
-    return allExpressions;
-}
-
-function _getNumberOfLinesForScript(scriptContents) {
-    let scriptLines = scriptContents.split("\n");
-    let scriptLineCount = scriptLines.length;
-
-    return scriptLineCount;
-}
-
-/*
- * The created array is a 1-1 representation of the hitcount in the filename. Each
- * element refers to an individual line. In order to avoid confusion, our array
- * is zero indexed, but the zero'th line is always ignored and the first element
- * refers to the first line of the file.
- *
- * A value of undefined for an element means that the line is non-executable and never actually
- * reached. A value of 0 means that it was executable but never reached. A positive value
- * indicates the hit count.
- *
- * We care about non-executable lines because we don't want to report coverage misses for
- * lines that could have never been executed anyways.
- *
- * The reason for using a 1-1 mapping as opposed to an array of key-value pairs for executable
- * lines is:
- *   1. Lookup speed is O(1) instead of O(log(n))
- *   2. There's a possibility we might hit a line which we thought was non-executable, in which
- *      case we can neatly handle the error by marking that line executable. A hit on a line
- *      we thought was non-executable is not as much of a problem as noise generated by
- *      ostensible "misses" which could in fact never be executed.
- *
- */
-function _expressionLinesToCounters(expressionLines, nLines) {
-    expressionLines.sort(function(left, right) { return left - right; });
-
-    let expressionLinesIndex = 0;
-    let counters = new Array(nLines + 1);
-
-    if (expressionLines.length === 0)
-        return counters;
-
-    for (let i = 1; i < counters.length; i++) {
-        if (!expressionLines.hasOwnProperty(expressionLinesIndex))
-            continue;
-        if (expressionLines[expressionLinesIndex] == i) {
-            counters[i] = 0;
-            expressionLinesIndex++;
-        }
-    }
-
-    return counters;
-}
-
-/* As above, we are creating a 1-1 representation of script lines to potential branches
- * where each element refers to a 1-index line (with the zero'th ignored).
- *
- * Each element is a GjsCoverageBranchData which, if the line at the element
- * position describes a branch, will be populated with a GjsReflectedScriptBranchInfo
- * and an array of unsigned each specifying the hit-count for each potential branch
- * in the branch info */
-function _branchesToBranchCounters(branches, nLines) {
-    branches.sort(function(left, right) {
-        return left.point - right.point;
-    });
-
-    let branchIndex = 0;
-    let counters = new Array(nLines + 1);
-
-    if (branches.length === 0)
-        return counters;
-
-    for (let i = 1; i < counters.length; i++) {
-        let branch = branches[branchIndex];
-        let branchPoint = branch.point;
-
-        if (branchPoint == i) {
-            counters[i] = {
-                point: branchPoint,
-                exits: branch.exits.map(function(exit) {
-                    return {
-                        line: exit,
-                        hitCount: 0
-                    };
-                }),
-                lastExit: (function() {
-                    let lastExitLine = 0;
-                    for (let exit of branch.exits) {
-                        if (lastExitLine < exit)
-                            lastExitLine = exit;
-                    }
-
-                    return lastExitLine;
-                })(),
-                hit: false
-            };
-
-            if (++branchIndex >= branches.length)
-                break;
-        }
-    }
-
-    return counters;
-}
-
-function _functionsToFunctionCounters(script, functions) {
-    let functionCounters = {};
-
-    functions.forEach(function(func) {
-        let [name, line, args] = func.key.split(':');
-
-        if (functionCounters[name] === undefined) {
-            functionCounters[name] = {};
-        }
-
-        if (functionCounters[name][line] === undefined) {
-            functionCounters[name][line] = {};
-        }
-
-        if (functionCounters[name][line][args] === undefined) {
-            functionCounters[name][line][args] = { hitCount: 0 };
-        } else {
-            log(script + ':' + line + ' Function identified as ' +
-                func.key + ' already seen in this file. Function coverage ' +
-                'will be incomplete.');
-        }
-    });
-
-    return functionCounters;
-}
-
-function _populateKnownFunctions(functions, nLines) {
-    let knownFunctions = new Array(nLines + 1);
-
-    functions.forEach(function(func) {
-        knownFunctions[func.line] = true;
-    });
-
-    return knownFunctions;
-}
-
-function _identifyFunctionCounterInLinePartForDescription(linePart,
-                                                          nArgs) {
-    /* There is only one potential option for this line. We might have been
-     * called with the wrong number of arguments, but it doesn't matter. */
-    if (Object.getOwnPropertyNames(linePart).length === 1)
-        return linePart[Object.getOwnPropertyNames(linePart)[0]];
-
-    /* Have to disambiguate using nArgs and we have an exact match. */
-    if (linePart[nArgs] !== undefined)
-        return linePart[nArgs];
-
-    /* There are multiple options on this line. Pick the one where
-     * we satisfy the number of arguments exactly, or failing that,
-     * go through each and pick the closest. */
-    let allNArgsOptions = Object.keys(linePart).map(function(key) {
-        return parseInt(key);
-    });
-
-    let closest = allNArgsOptions.reduce(function(prev, current, index, array) {
-        let nArgsOption = array[index];
-        if (Math.abs(nArgsOption - nArgs) < Math.abs(current - nArgs)) {
-            return nArgsOption;
-        }
-
-        return current;
-    });
-
-    return linePart[String(closest)];
-}
-
-function _identifyFunctionCounterForDescription(functionCounters,
-                                                name,
-                                                line,
-                                                nArgs) {
-    let candidateCounter = functionCounters[name];
-
-    if (candidateCounter === undefined)
-        return null;
-
-    if (Object.getOwnPropertyNames(candidateCounter).length === 1) {
-        let linePart = candidateCounter[Object.getOwnPropertyNames(candidateCounter)[0]];
-        return _identifyFunctionCounterInLinePartForDescription(linePart, nArgs);
-    }
-
-    let linePart = functionCounters[name][line];
-
-    if (linePart === undefined) {
-        return null;
-    }
-
-    return _identifyFunctionCounterInLinePartForDescription(linePart, nArgs);
-}
-
-/**
- * _incrementFunctionCounters
- *
- * functionCounters: An object which is a key-value pair with the following schema:
- * {
- *      "key" : { line, hitCount }
- * }
- * linesWithKnownFunctions: An array of either "true" or undefined, with true set to
- * each element corresponding to a line that we know has a function on it.
- * name: The name of the function or "(anonymous)" if it has no name
- * line: The line at which execution first started on this function.
- * nArgs: The number of arguments this function has.
- */
-function _incrementFunctionCounters(functionCounters,
-                                    linesWithKnownFunctions,
-                                    name,
-                                    line,
-                                    nArgs) {
-    let functionCountersForKey = _identifyFunctionCounterForDescription(functionCounters,
-                                                                        name,
-                                                                        line,
-                                                                        nArgs);
-
-    /* Its possible that the JS Engine might enter a funciton
-     * at an executable line which is a little bit past the
-     * actual definition. Roll backwards until we reach the
-     * last known function definition line which we kept
-     * track of earlier to see if we can find this function first */
-    if (functionCountersForKey === null) {
-        do {
-            --line;
-            functionCountersForKey = _identifyFunctionCounterForDescription(functionCounters,
-                                                                            name,
-                                                                            line,
-                                                                            nArgs);
-        } while (linesWithKnownFunctions[line] !== true && line > 0);
-    }
-
-    if (functionCountersForKey !== null) {
-        functionCountersForKey.hitCount++;
-    } else {
-        let functionKey = [name, line, nArgs].join(':');
-        throw new Error("expected Reflect to find function " + functionKey);
-    }
-}
-
-/**
- * _incrementExpressionCounters
- *
- * expressonCounters: An array of either a hit count for a found
- * executable line or undefined for a known non-executable line.
- * line: an executed line
- * @shouldWarn: if true, print a mostly harmless warning about executing a line
- * that was thought non-executable.
- */
-function _incrementExpressionCounters(expressionCounters,
-                                      script,
-                                      offsetLine, shouldWarn) {
-    let expressionCountersLen = expressionCounters.length;
-
-    if (offsetLine >= expressionCountersLen)
-        throw new Error("Executed line " + offsetLine + " which was past the highest-found line " + 
expressionCountersLen);
-
-    /* If this happens it is not a huge problem - though it does
-     * mean that the reflection machinery is not doing its job, so we should
-     * print a debug message about it in case someone is interested.
-     *
-     * The reason why we don't have a proper log is because it
-     * is difficult to determine what the SpiderMonkey program counter
-     * will actually pass over, especially function declarations for some
-     * reason:
-     *
-     *     function f(a,b) {
-     *         a = 1;
-     *     }
-     *
-     * In some cases, the declaration itself will be executed
-     * but in other cases it won't be. Reflect.parse tells us that
-     * the only two expressions on that line are a FunctionDeclaration
-     * and BlockStatement, neither of which would ordinarily be
-     * executed */
-    if (expressionCounters[offsetLine] === undefined) {
-        if (shouldWarn)
-            log(script + ':' + offsetLine + ' Executed line previously marked ' +
-                'non-executable by Reflect');
-        expressionCounters[offsetLine] = 0;
-    }
-
-    expressionCounters[offsetLine]++;
-}
-
-var _BranchTracker = class {
-    constructor(branchCounters) {
-        this._branchCounters = branchCounters;
-        this._activeBranch = undefined;
-    }
-
-    incrementBranchCounters(offsetLine) {
-        /* Set branch exits or find a new active branch */
-        if (this._activeBranch) {
-            this._activeBranch.exits.forEach(function(exit) {
-                if (exit.line === offsetLine) {
-                    exit.hitCount++;
-                }
-            });
-
-            /* Only set the active branch to undefined once we're
-             * completely outside of it, since we might be in a case statement where
-             * we need to check every possible option before jumping to an
-             * exit */
-            if (offsetLine >= this._activeBranch.lastExit)
-                this._activeBranch = undefined;
-        }
-
-        let nextActiveBranch = this._branchCounters[offsetLine];
-        if (nextActiveBranch !== undefined) {
-            this._activeBranch = nextActiveBranch;
-            this._activeBranch.hit = true;
-        }
-    }
-};
-
-function _convertFunctionCountersToArray(functionCounters) {
-    let arrayReturn = [];
-    /* functionCounters is an object so explore it to create a
-     * set of function keys and then convert it to
-     * an array-of-object using the key as a property
-     * of that object */
-    for (let name of Object.getOwnPropertyNames(functionCounters)) {
-        let namePart = functionCounters[name];
-
-        for (let line of Object.getOwnPropertyNames(namePart)) {
-            let linePart = functionCounters[name][line];
-
-            for (let nArgs of Object.getOwnPropertyNames(linePart)) {
-                let functionKey = [name, line, nArgs].join(':');
-                arrayReturn.push({ name: functionKey,
-                                   line: Number(line),
-                                   nArgs: nArgs,
-                                   hitCount: linePart[nArgs].hitCount });
-            }
-        }
-    }
-
-    arrayReturn.sort(function(left, right) {
-        if (left.name < right.name)
-            return -1;
-        else if (left.name > right.name)
-            return 1;
-        else
-            return 0;
-    });
-    return arrayReturn;
-}
-
-/* Looks up filename in cache and fetches statistics
- * directly from the cache */
-function _fetchCountersFromCache(filename, cache, nLines) {
-    if (!cache)
-        return null;
-
-    if (Object.keys(cache).indexOf(filename) !== -1) {
-        let cache_for_file = cache[filename];
-
-        if (cache_for_file.mtime) {
-             let mtime = getFileModificationTime(filename);
-             if (mtime[0] != cache[filename].mtime[0] ||
-                 mtime[1] != cache[filename].mtime[1])
-                 return null;
-        } else {
-            let checksum = getFileChecksum(filename);
-            if (checksum != cache[filename].checksum)
-                return null;
-        }
-
-        let functions = cache_for_file.functions;
-
-        return {
-            expressionCounters: _expressionLinesToCounters(cache_for_file.lines, nLines),
-            branchCounters: _branchesToBranchCounters(cache_for_file.branches, nLines),
-            functionCounters: _functionsToFunctionCounters(filename, functions),
-            linesWithKnownFunctions: _populateKnownFunctions(functions, nLines),
-            nLines: nLines
-        };
-    }
-
-    return null;
-}
-
-function _fetchCountersFromReflection(filename, contents, nLines) {
-    // Shebang is illegal character sequence to JS parser
-    if (contents.startsWith('#!'))
-        contents = '//' + contents;
-    let reflection = Reflect.parse(contents);
-    let functions = functionsForAST(reflection);
-
-    return {
-        expressionCounters: _expressionLinesToCounters(expressionLinesForAST(reflection), nLines),
-        branchCounters: _branchesToBranchCounters(branchesForAST(reflection), nLines),
-        functionCounters: _functionsToFunctionCounters(filename, functions),
-        linesWithKnownFunctions: _populateKnownFunctions(functions, nLines),
-        nLines: nLines
-    };
-}
-
-var CoverageStatisticsContainer = class {
-    constructor(prefixes, cache) {
-        /* Copy the files array, so that it can be re-used in the tests */
-        this._cachedASTs = cache ? JSON.parse(cache) : null;
-        this._coveredFiles = {};
-        this._cacheMisses = 0;
-    }
-
-    _createStatisticsFor(filename) {
-        let contents = getFileContents(filename);
-        let nLines = _getNumberOfLinesForScript(contents);
-
-        let counters = _fetchCountersFromCache(filename, this._cachedASTs,
-            nLines);
-        if (counters === null) {
-            this._cacheMisses++;
-            counters = _fetchCountersFromReflection(filename, contents, nLines);
-        }
-
-        if (counters === null)
-            throw new Error('Failed to parse and reflect file ' + filename);
-
-        /* Set contents here as we don't pass it to _fetchCountersFromCache. */
-        counters.contents = contents;
-
-        return counters;
-    }
-
-    _ensureStatisticsFor(filename) {
-        // Skip scripts fed to JS engine programmatically.
-        if (filename.startsWith('<') && filename.endsWith('>'))
-            return undefined;
-        if (!this._coveredFiles[filename])
-            this._coveredFiles[filename] = this._createStatisticsFor(filename);
-        return this._coveredFiles[filename];
-    }
-
-    stringify() {
-        let cache_data = {};
-        Object.keys(this._coveredFiles).forEach(filename => {
-            let statisticsForFilename = this._coveredFiles[filename];
-            let mtime = getFileModificationTime(filename);
-            let cacheDataForFilename = {
-                mtime: mtime,
-                checksum: mtime === null ? getFileChecksum(filename) : null,
-                lines: [],
-                branches: [],
-                functions: 
_convertFunctionCountersToArray(statisticsForFilename.functionCounters).map(function(func) {
-                    return {
-                        key: func.name,
-                        line: func.line
-                    };
-                })
-            };
-
-            /* We're using a index based loop here since we need access to the
-             * index, since it actually represents the current line number
-             * on the file (see _expressionLinesToCounters). */
-            for (let line_index = 0;
-                 line_index < statisticsForFilename.expressionCounters.length;
-                 ++line_index) {
-                 if (statisticsForFilename.expressionCounters[line_index] !== undefined)
-                     cacheDataForFilename.lines.push(line_index);
-
-                 if (statisticsForFilename.branchCounters[line_index] !== undefined) {
-                     let branchCounters = statisticsForFilename.branchCounters[line_index];
-                     cacheDataForFilename.branches.push({
-                         point: statisticsForFilename.branchCounters[line_index].point,
-                         exits: statisticsForFilename.branchCounters[line_index].exits.map(function(exit) {
-                             return exit.line;
-                         })
-                     });
-                 }
-            }
-            cache_data[filename] = cacheDataForFilename;
-        });
-        return JSON.stringify(cache_data);
-    }
-
-    getCoveredFiles() {
-        return Object.keys(this._coveredFiles);
-    }
-
-    fetchStatistics(filename) {
-        return this._ensureStatisticsFor(filename);
-    }
-
-    staleCache() {
-        return this._cacheMisses > 0;
-    }
-
-    deleteStatistics(filename) {
-        this._coveredFiles[filename] = undefined;
-    }
-};
-
-/**
- * Main class tying together the Debugger object and CoverageStatisticsContainer.
- *
- * It isn't possible to unit test this class because it depends on running
- * Debugger which in turn depends on objects injected in from another compartment */
-var CoverageStatistics = class {
-    constructor(prefixes, cache, shouldWarn) {
-        let _shouldWarn = shouldWarn;  // capture in closure
-        let container = new CoverageStatisticsContainer(prefixes, cache);
-        this.container = container;
-
-        /* 'debuggee' comes from the invocation from
-         * a separate compartment inside of coverage.cpp */
-        this.dbg = new Debugger(debuggee);
-        this.dbg.onEnterFrame = function (frame) {
-            let statistics;
-
-            try {
-                statistics = container.fetchStatistics(frame.script.url);
-                if (!statistics) {
-                    return undefined;
-                }
-            } catch (e) {
-                /* We don't care about this frame, return */
-                log(`${e.message} ${e.stack}`);
-                return undefined;
-            }
-
-            function _logExceptionAndReset(exception, callee, line) {
-                log(`${exception.fileName}:${exception.lineNumber} (processing ` +
-                    `${frame.script.url}:${callee}:${line}) - ${exception.message}`);
-                log('Will not log statistics for this file');
-                frame.onStep = undefined;
-                frame._branchTracker = undefined;
-                container.deleteStatistics(frame.script.url);
-            }
-
-            /* Log function calls */
-            if (frame.callee !== null && frame.callee.callable) {
-                let name = frame.callee.name || '(anonymous)';
-                let {lineNumber} = frame.script.getOffsetLocation(frame.offset);
-                let nArgs = frame.callee.parameterNames.length;
-
-                try {
-                    _incrementFunctionCounters(statistics.functionCounters,
-                        statistics.linesWithKnownFunctions, name, lineNumber,
-                        nArgs);
-                } catch (e) {
-                    /* Something bad happened. Log the exception and delete
-                     * statistics for this file */
-                    _logExceptionAndReset(e, name, lineNumber);
-                    return undefined;
-                }
-            }
-
-            /* Upon entering the frame, the active branch is always inactive */
-            frame._branchTracker = new _BranchTracker(statistics.branchCounters);
-
-            /* Set single-step hook */
-            frame.onStep = function() {
-                /* Line counts */
-                let {offset} = this;
-                let {lineNumber} = this.script.getOffsetLocation(offset);
-
-                try {
-                    _incrementExpressionCounters(statistics.expressionCounters,
-                        frame.script.url, lineNumber, _shouldWarn);
-                    this._branchTracker.incrementBranchCounters(lineNumber);
-                } catch (e) {
-                    /* Something bad happened. Log the exception and delete
-                     * statistics for this file */
-                    _logExceptionAndReset(e, frame.callee, lineNumber);
-                }
-            };
-
-            return undefined;
-        };
-    }
-
-    getCoveredFiles() {
-        return this.container.getCoveredFiles();
-    }
-
-    staleCache() {
-        return this.container.staleCache();
-    }
-
-    stringify() {
-        return this.container.stringify();
-    }
-
-    getNumberOfLinesFor(filename) {
-        return this.container.fetchStatistics(filename).nLines;
-    }
-
-    getExecutedLinesFor(filename) {
-        return this.container.fetchStatistics(filename).expressionCounters;
-    }
-
-    getBranchesFor(filename) {
-        return this.container.fetchStatistics(filename).branchCounters;
-    }
-
-    getFunctionsFor(filename) {
-        let {functionCounters} = this.container.fetchStatistics(filename);
-        return _convertFunctionCountersToArray(functionCounters);
-    }
-
-    deactivate() {
-        /* This property is designed to be a one-stop-shop to
-         * disable the debugger for this debuggee, without having
-         * to traverse all its scripts or frames */
-        this.dbg.enabled = false;
-    }
-};
+    exports.debugger = new Debugger(exports.debuggee);
+    exports.debugger.collectCoverageInfo = true;
+})(window);
diff --git a/test/gjs-test-coverage.cpp b/test/gjs-test-coverage.cpp
index 171a1c4..765e6a7 100644
--- a/test/gjs-test-coverage.cpp
+++ b/test/gjs-test-coverage.cpp
@@ -39,7 +39,6 @@
 #include <gjs/gjs.h>
 
 #include "gjs/coverage.h"
-#include "gjs/coverage-internal.h"
 #include "gjs/jsapi-util.h"
 #include "gjs-test-utils.h"
 
@@ -91,7 +90,7 @@ gjs_coverage_fixture_set_up(gpointer      fixture_data,
                             gconstpointer user_data)
 {
     GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-    const char         *js_script = "function f() { return 1; }\n";
+    const char         *js_script = "\nvar f = function () { return 1; }\n";
 
     char *tmp_output_dir_name = g_strdup("/tmp/gjs_coverage_tmp.XXXXXX");
     tmp_output_dir_name = mkdtemp(tmp_output_dir_name);
@@ -123,9 +122,8 @@ gjs_coverage_fixture_set_up(gpointer      fixture_data,
     };
 
     fixture->context = gjs_context_new_with_search_path((char **) search_paths);
-    fixture->coverage =
-        gjs_coverage_new_internal_without_cache(coverage_paths, fixture->context,
-                                                fixture->lcov_output_dir);
+    fixture->coverage = gjs_coverage_new(coverage_paths, fixture->context,
+                                         fixture->lcov_output_dir);
 
     replace_file(fixture->tmp_js_script, js_script);
     g_free(tmp_output_dir_name);
@@ -268,44 +266,6 @@ assert_coverage_data_matches_values_for_key(const char            *data,
     g_assert_cmpuint(n, ==, 0);
 }
 
-/* A simple wrapper around gjs_coverage_new */
-static GjsCoverage *
-create_coverage_for_script(GjsContext *context,
-                           GFile      *script,
-                           GFile      *output_dir)
-{
-    char *script_path = get_script_identifier(script);
-    char *coverage_scripts[] = {
-        script_path,
-        NULL
-    };
-
-    GjsCoverage *retval =
-        gjs_coverage_new_internal_without_cache(coverage_scripts, context,
-                                                output_dir);
-    g_free(script_path);
-    return retval;
-}
-
-static GjsCoverage *
-create_coverage_for_script_and_cache(GjsContext *context,
-                                     GFile      *cache,
-                                     GFile      *script,
-                                     GFile      *output_dir)
-{
-    char *script_path = get_script_identifier(script);
-    char *coverage_scripts[] = {
-        script_path,
-        NULL
-    };
-
-    GjsCoverage *retval =
-        gjs_coverage_new_internal_with_cache(coverage_scripts, context,
-                                             output_dir, cache);
-    g_free(script_path);
-    return retval;
-}
-
 static void
 test_covered_file_is_duplicated_into_output_if_resource(gpointer      fixture_data,
                                                         gconstpointer user_data)
@@ -327,10 +287,8 @@ test_covered_file_is_duplicated_into_output_if_resource(gpointer      fixture_da
     };
 
     fixture->context = gjs_context_new_with_search_path(search_paths);
-    fixture->coverage =
-        gjs_coverage_new_internal_without_cache(coverage_scripts,
-                                                fixture->context,
-                                                fixture->lcov_output_dir);
+    fixture->coverage = gjs_coverage_new(coverage_scripts, fixture->context,
+                                         fixture->lcov_output_dir);
 
     gjs_context_eval_file(fixture->context,
                           mock_resource_filename,
@@ -472,10 +430,8 @@ test_expected_entry_not_written_for_nonexistent_file(gpointer      fixture_data,
     };
 
     g_object_unref(fixture->coverage);
-    fixture->coverage =
-        gjs_coverage_new_internal_without_cache(coverage_paths,
-                                                fixture->context,
-                                                fixture->lcov_output_dir);
+    fixture->coverage = gjs_coverage_new(coverage_paths, fixture->context,
+                                         fixture->lcov_output_dir);
 
     /* Temporarily disable fatal mask and silence warnings */
     GLogLevelFlags old_flags = g_log_set_always_fatal((GLogLevelFlags) G_LOG_LEVEL_ERROR);
@@ -575,8 +531,8 @@ test_single_branch_coverage_written_to_coverage_data(gpointer      fixture_data,
                                           NULL);
 
     const BranchLineData expected_branches[] = {
-        { 2, 0, NOT_TAKEN },
-        { 2, 1, TAKEN }
+        { 2, 0, TAKEN },
+        { 2, 1, NOT_EXECUTED }
     };
     const gsize expected_branches_len = G_N_ELEMENTS(expected_branches);
 
@@ -678,7 +634,7 @@ test_branches_for_multiple_case_statements_fallthrough(gpointer      fixture_dat
     const BranchLineData expected_branches[] = {
         { 3, 0, TAKEN },
         { 3, 1, TAKEN },
-        { 3, 2, NOT_TAKEN }
+        { 3, 2, NOT_EXECUTED }
     };
     const gsize expected_branches_len = G_N_ELEMENTS(expected_branches);
 
@@ -781,11 +737,10 @@ test_function_names_written_to_coverage_data(gpointer      fixture_data,
                                           fixture->lcov_output,
                                           NULL);
 
-    /* The internal hash table is sorted in alphabetical order
-     * so the function names need to be in this order too */
     const char * expected_function_names[] = {
-        "(anonymous):2:0",
-        "f:1:0"
+        "top-level",
+        "f",
+        "b",
     };
     const gsize expected_function_names_len = G_N_ELEMENTS(expected_function_names);
 
@@ -832,6 +787,7 @@ test_function_lines_written_to_coverage_data(gpointer      fixture_data,
                                           NULL);
     const char * const expected_function_lines[] = {
         "1",
+        "1",
         "3"
     };
     const gsize expected_function_lines_len = G_N_ELEMENTS(expected_function_lines);
@@ -902,11 +858,10 @@ test_function_hit_counts_for_big_functions_written_to_coverage_data(gpointer
                                           fixture->lcov_output,
                                           NULL);
 
-    /* The internal hash table is sorted in alphabetical order
-     * so the function names need to be in this order too */
     const FunctionHitCountData expected_hit_counts[] = {
-        { "(anonymous):6:0", 1 },
-        { "f:1:0", 1 }
+        { "top-level", 1 },
+        { "f", 1 },
+        { "b", 1 },
     };
 
     const gsize expected_hit_count_len = G_N_ELEMENTS(expected_hit_counts);
@@ -947,12 +902,10 @@ test_function_hit_counts_for_little_functions_written_to_coverage_data(gpointer
                                           fixture->lcov_output,
                                           NULL);
 
-    /* The internal hash table is sorted in alphabetical order
-     * so the function names need to be in this order too */
     const FunctionHitCountData expected_hit_counts[] = {
-        { "(anonymous):2:0", 0 },
-        { "(anonymous):4:0", 1 },
-        { "f:1:0", 1 }
+        { "top-level", 1 },
+        { "f", 1 },
+        { "b", 1 },
     };
 
     const gsize expected_hit_count_len = G_N_ELEMENTS(expected_hit_counts);
@@ -989,11 +942,10 @@ test_function_hit_counts_written_to_coverage_data(gpointer      fixture_data,
                                           fixture->lcov_output,
                                           NULL);
 
-    /* The internal hash table is sorted in alphabetical order
-     * so the function names need to be in this order too */
     const FunctionHitCountData expected_hit_counts[] = {
-        { "(anonymous):2:0", 1 },
-        { "f:1:0", 1 }
+        { "top-level", 1 },
+        { "f", 1 },
+        { "b", 1 },
     };
 
     const gsize expected_hit_count_len = G_N_ELEMENTS(expected_hit_counts);
@@ -1031,9 +983,9 @@ test_total_function_coverage_written_to_coverage_data(gpointer      fixture_data
 
     /* More than one assert per test is bad, but we are testing interlinked concepts */
     assert_coverage_data_contains_value_for_key(coverage_data_contents,
-                                                "FNF:", "2");
+                                                "FNF:", "3");
     assert_coverage_data_contains_value_for_key(coverage_data_contents,
-                                                "FNH:", "1");
+                                                "FNH:", "2");
     g_free(coverage_data_contents);
 }
 
@@ -1080,7 +1032,7 @@ test_single_line_hit_written_to_coverage_data(gpointer      fixture_data,
                                           NULL);
 
     const LineCountIsMoreThanData data = {
-        1,
+        2,  /* FIXME: line 1 is never hit */
         0
     };
 
@@ -1114,7 +1066,6 @@ test_hits_on_multiline_if_cond(gpointer      fixture_data,
 
     /* Hits on all lines, including both lines with a condition (3 and 4) */
     const LineCountIsMoreThanData data[] = {
-        { 1, 0 },
         { 2, 0 },
         { 3, 0 },
         { 4, 0 }
@@ -1159,7 +1110,7 @@ test_no_hits_to_coverage_data_for_unexecuted(gpointer      fixture_data,
                                                NULL);
 
     /* No files were executed, so the coverage data is empty. */
-    g_assert_cmpstr(coverage_data_contents, ==, "");
+    g_assert_cmpstr(coverage_data_contents, ==, "\n");
     g_free(coverage_data_contents);
 }
 
@@ -1216,9 +1167,8 @@ gjs_coverage_multiple_source_files_to_single_output_fixture_set_up(gpointer fixt
 
     fixture->base_fixture.context = gjs_context_new_with_search_path(search_paths);
     fixture->base_fixture.coverage =
-        gjs_coverage_new_internal_without_cache(coverage_paths,
-                                                fixture->base_fixture.context,
-                                                fixture->base_fixture.lcov_output_dir);
+        gjs_coverage_new(coverage_paths, fixture->base_fixture.context,
+                         fixture->base_fixture.lcov_output_dir);
 
     g_free(output_path);
     g_free(first_js_script_path);
@@ -1227,7 +1177,7 @@ gjs_coverage_multiple_source_files_to_single_output_fixture_set_up(gpointer fixt
     char *base_name = g_file_get_basename(fixture->base_fixture.tmp_js_script);
     char *base_name_without_extension = g_strndup(base_name,
                                                   strlen(base_name) - 3);
-    char *mock_script = g_strconcat("const FirstScript = imports.",
+    char *mock_script = g_strconcat("\nconst FirstScript = imports.",
                                     base_name_without_extension,
                                     ";\n",
                                     "let a = FirstScript.f;\n"
@@ -1322,17 +1272,17 @@ test_correct_line_coverage_data_written_for_both_source_file_sectons(gpointer
                                           NULL);
 
     LineCountIsMoreThanData first_script_matcher = {
-        1,
+        2,  /* FIXME: line 1 is never hit */
         0
     };
 
     LineCountIsMoreThanData second_script_matchers[] = {
         {
-            1,
+            2,  /* FIXME: line 1 is never hit */
             0
         },
         {
-            2,
+            3,  /* FIXME: line 1 is never hit */
             0
         }
     };
@@ -1374,664 +1324,6 @@ test_correct_line_coverage_data_written_for_both_source_file_sectons(gpointer
     g_free(coverage_data_contents);
 }
 
-static GString *
-append_tuples_to_array_in_object_notation(GString    *string,
-                                          const char  *tuple_contents_strv)
-{
-    char *original_ptr = (char *) tuple_contents_strv;
-    char *expected_tuple_contents = NULL;
-    while ((expected_tuple_contents = strsep((char **) &tuple_contents_strv, ";")) != NULL) {
-       if (!strlen(expected_tuple_contents))
-           continue;
-
-       if (expected_tuple_contents != original_ptr)
-           g_string_append_printf(string, ",");
-        g_string_append_printf(string, "{%s}", expected_tuple_contents);
-    }
-
-    return string;
-}
-
-static GString *
-format_expected_cache_object_notation(const char *mtimes,
-                                      const char *hash,
-                                      GFile      *script,
-                                      const char *expected_executable_lines_array,
-                                      const char *expected_branches,
-                                      const char *expected_functions)
-{
-    char *script_name = get_script_identifier(script);
-    GString *string = g_string_new("");
-    g_string_append_printf(string,
-                           "{\"%s\":{\"mtime\":%s,\"checksum\":%s,\"lines\":[%s],\"branches\":[",
-                           script_name,
-                           mtimes,
-                           hash,
-                           expected_executable_lines_array);
-    g_free(script_name);
-    append_tuples_to_array_in_object_notation(string, expected_branches);
-    g_string_append_printf(string, "],\"functions\":[");
-    append_tuples_to_array_in_object_notation(string, expected_functions);
-    g_string_append_printf(string, "]}}");
-    return string;
-}
-
-typedef struct _GjsCoverageCacheObjectNotationTestTableData {
-    const char *test_name;
-    const char *script;
-    const char *uri;
-    const char *expected_executable_lines;
-    const char *expected_branches;
-    const char *expected_functions;
-} GjsCoverageCacheObjectNotationTableTestData;
-
-static GBytes *
-serialize_ast_to_bytes(GjsCoverage *coverage,
-                       const char **coverage_paths)
-{
-    return gjs_serialize_statistics(coverage);
-}
-
-static char *
-serialize_ast_to_object_notation(GjsCoverage *coverage,
-                                 const char **coverage_paths)
-{
-    /* Unfortunately, we need to pass in this paramater here since
-     * the len parameter is not allow-none.
-     *
-     * The caller doesn't need to know about the length of the
-     * data since it is only used for strcmp and the data is
-     * NUL-terminated anyway. */
-    gsize len = 0;
-    return (char *)g_bytes_unref_to_data(serialize_ast_to_bytes(coverage, coverage_paths),
-                                         &len);
-}
-
-static char *
-eval_file_for_ast_in_object_notation(GjsContext  *context,
-                                     GjsCoverage *coverage,
-                                     GFile       *script)
-{
-    bool success = eval_script(context, script);
-    g_assert_true(success);
-
-    char *filename = g_file_get_path(script);
-    const gchar *coverage_paths[] = {
-        filename,
-        NULL
-    };
-
-    char *retval = serialize_ast_to_object_notation(coverage, coverage_paths);
-    g_free(filename);
-    return retval;
-}
-
-static void
-test_coverage_cache_data_in_expected_format(gpointer      fixture_data,
-                                            gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-    GjsCoverageCacheObjectNotationTableTestData *table_data = (GjsCoverageCacheObjectNotationTableTestData 
*) user_data;
-
-    replace_file(fixture->tmp_js_script, table_data->script);
-    char *cache_in_object_notation = eval_file_for_ast_in_object_notation(fixture->context,
-                                                                          fixture->coverage,
-                                                                          fixture->tmp_js_script);
-    g_assert(cache_in_object_notation != NULL);
-
-    /* Sleep for a little while to make sure that the new file has a
-     * different mtime */
-    sleep(1);
-
-    GTimeVal mtime;
-    bool successfully_got_mtime = gjs_get_file_mtime(fixture->tmp_js_script, &mtime);
-    g_assert_true(successfully_got_mtime);
-
-    char *mtime_string = g_strdup_printf("[%li,%li]", mtime.tv_sec, mtime.tv_usec);
-    GString *expected_cache_object_notation = format_expected_cache_object_notation(mtime_string,
-                                                                                    "null",
-                                                                                    fixture->tmp_js_script,
-                                                                                    
table_data->expected_executable_lines,
-                                                                                    
table_data->expected_branches,
-                                                                                    
table_data->expected_functions);
-
-    g_assert_cmpstr(cache_in_object_notation, ==, expected_cache_object_notation->str);
-
-    g_string_free(expected_cache_object_notation, true);
-    g_free(cache_in_object_notation);
-    g_free(mtime_string);
-}
-
-static void
-test_coverage_cache_data_in_expected_format_resource(gpointer      fixture_data,
-                                                     gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-    GjsCoverageCacheObjectNotationTableTestData *table_data = (GjsCoverageCacheObjectNotationTableTestData 
*) user_data;
-
-    GFile *resource = g_file_new_for_uri(table_data->uri);
-
-    char *hash_string_no_quotes = gjs_get_file_checksum(resource);
-
-    char *hash_string = g_strdup_printf("\"%s\"", hash_string_no_quotes);
-    g_free(hash_string_no_quotes);
-
-    GString *expected_cache_object_notation = format_expected_cache_object_notation("null",
-                                                                                    hash_string,
-                                                                                    resource,
-                                                                                    
table_data->expected_executable_lines,
-                                                                                    
table_data->expected_branches,
-                                                                                    
table_data->expected_functions);
-
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script(fixture->context, resource,
-                                                   fixture->tmp_output_dir);
-    char *cache_in_object_notation = eval_file_for_ast_in_object_notation(fixture->context,
-                                                                          fixture->coverage,
-                                                                          resource);
-    g_object_unref(resource);
-
-    g_assert_cmpstr(cache_in_object_notation, ==, expected_cache_object_notation->str);
-
-    g_string_free(expected_cache_object_notation, true);
-    g_free(cache_in_object_notation);
-    g_free(hash_string);
-}
-
-static char *
-generate_coverage_compartment_verify_script(GFile      *coverage_script,
-                                            const char *user_script)
-{
-    char *coverage_script_filename = g_file_get_path(coverage_script);
-    char *retval =
-        g_strdup_printf("const covered_script_filename = '%s';\n"
-                        "function assertEquals(lhs, rhs) {\n"
-                        "    if (lhs !== rhs)\n"
-                        "        throw new Error('Assertion failure');\n"
-                        "}\n"
-                        "function assertArrayEquals(lhs, rhs) {\n"
-                        "    assertEquals(lhs.length, rhs.length);\n"
-                        "    for (let i = 0; i < lhs.length; i++)\n"
-                        "        assertEquals(lhs[i], rhs[i]);\n"
-                        "}\n"
-                        "\n"
-                        "%s", coverage_script_filename, user_script);
-    g_free(coverage_script_filename);
-    return retval;
-}
-
-typedef struct _GjsCoverageCacheJSObjectTableTestData {
-    const char *test_name;
-    const char *script;
-    const char *verify_js_script;
-} GjsCoverageCacheJSObjectTableTestData;
-
-static void
-test_coverage_cache_as_js_object_has_expected_properties(gpointer      fixture_data,
-                                                         gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-    GjsCoverageCacheJSObjectTableTestData *table_data = (GjsCoverageCacheJSObjectTableTestData *) user_data;
-
-    replace_file(fixture->tmp_js_script, table_data->script);
-    eval_script(fixture->context, fixture->tmp_js_script);
-
-    char *script_filename = g_file_get_path(fixture->tmp_js_script);
-    const gchar *coverage_paths[] = {
-        script_filename,
-        NULL
-    };
-
-    GBytes *cache = serialize_ast_to_bytes(fixture->coverage, coverage_paths);
-    auto cx = static_cast<JSContext *>(gjs_context_get_native_context(fixture->context));
-    JS::RootedValue cache_result_value(cx,
-        JS::StringValue(gjs_deserialize_cache_to_object(fixture->coverage, cache)));
-    gjs_inject_value_into_coverage_compartment(fixture->coverage,
-                                               cache_result_value,
-                                               "coverage_cache");
-
-    char *verify_script_complete = generate_coverage_compartment_verify_script(fixture->tmp_js_script,
-                                                                               table_data->verify_js_script);
-    gjs_run_script_in_coverage_compartment(fixture->coverage,
-                                           verify_script_complete);
-    g_free(verify_script_complete);
-    g_free(script_filename);
-    g_bytes_unref(cache);
-}
-
-typedef struct _GjsCoverageCacheEqualResultsTableTestData {
-    const char *test_name;
-    const char *script;
-} GjsCoverageCacheEqualResultsTableTestData;
-
-
-static GFile *
-get_coverage_tmp_cache(void)
-{
-    GFileIOStream *stream;
-    GError *error = NULL;
-    GFile *cache_file = g_file_new_tmp("gjs-coverage-cache-XXXXXX", &stream, &error);
-    g_assert_no_error(error);
-    g_assert_nonnull(cache_file);
-    g_object_unref(stream);
-
-    return cache_file;
-}
-
-static GFile *
-write_cache_to_temporary_file(GBytes *cache)
-{
-    GFile *temporary_file = get_coverage_tmp_cache();
-
-    if (!gjs_write_cache_file(temporary_file, cache)) {
-        g_object_unref(temporary_file);
-        return NULL;
-    }
-
-    return temporary_file;
-}
-
-static GFile *
-serialize_ast_to_cache_in_temporary_file(GjsCoverage *coverage,
-                                         const char  **coverage_paths)
-{
-    GBytes   *cache = serialize_ast_to_bytes(coverage, coverage_paths);
-    GFile *cache_file = write_cache_to_temporary_file(cache);
-
-    g_bytes_unref(cache);
-
-    return cache_file;
-}
-
-static void
-test_coverage_cache_equal_results_to_reflect_parse(gpointer      fixture_data,
-                                                   gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-    GjsCoverageCacheEqualResultsTableTestData *equal_results_data = 
(GjsCoverageCacheEqualResultsTableTestData *) user_data;
-
-    replace_file(fixture->tmp_js_script, equal_results_data->script);
-
-    char *tmp_js_script_filename = g_file_get_path(fixture->tmp_js_script);
-    const gchar *coverage_paths[] = {
-        tmp_js_script_filename,
-        NULL
-    };
-
-    char *coverage_data_contents_no_cache =
-        eval_script_and_get_coverage_data(fixture->context,
-                                          fixture->coverage,
-                                          fixture->tmp_js_script,
-                                          fixture->lcov_output,
-                                          NULL);
-    GFile *cache_file = serialize_ast_to_cache_in_temporary_file(fixture->coverage,
-                                                                 coverage_paths);
-    g_assert_nonnull(cache_file);
-
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             fixture->tmp_js_script,
-                                                             fixture->lcov_output_dir);
-    g_object_unref(cache_file);
-
-    /* Overwrite tracefile with nothing and start over */
-    replace_file(fixture->lcov_output, "");
-
-    char *coverage_data_contents_cached =
-        eval_script_and_get_coverage_data(fixture->context,
-                                          fixture->coverage,
-                                          fixture->tmp_js_script,
-                                          fixture->lcov_output,
-                                          NULL);
-
-    g_assert_cmpstr(coverage_data_contents_cached, ==, coverage_data_contents_no_cache);
-
-    g_free(coverage_data_contents_cached);
-    g_free(coverage_data_contents_no_cache);
-    g_free(tmp_js_script_filename);
-}
-
-static GFile *
-eval_file_for_tmp_ast_cache(GjsContext  *context,
-                            GjsCoverage *coverage,
-                            GFile       *script)
-{
-    bool success = eval_script(context, script);
-    g_assert_true(success);
-
-    char *filename = g_file_get_path(script);
-    const gchar *coverage_paths[] = {
-        filename,
-        NULL
-    };
-
-    GFile *retval = serialize_ast_to_cache_in_temporary_file(coverage,
-                                                             coverage_paths);
-    g_free(filename);
-    return retval;
-}
-
-/* Effectively, the results should be what we expect even though
- * we overwrote the original script after getting coverage and
- * fetching the cache */
-static void
-test_coverage_cache_invalidation(gpointer      fixture_data,
-                                 gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-
-    GFile *cache_file = eval_file_for_tmp_ast_cache(fixture->context,
-                                                    fixture->coverage,
-                                                    fixture->tmp_js_script);
-
-    /* Sleep for a little while to make sure that the new file has a
-     * different mtime */
-    sleep(1);
-
-    /* Overwrite tracefile with nothing */
-    replace_file(fixture->lcov_output, "");
-
-    /* Write a new script into the temporary js file, which will be
-     * completely different to the original script that was there */
-    replace_file(fixture->tmp_js_script,
-                 "let i = 0;\n"
-                 "let j = 0;\n");
-
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             fixture->tmp_js_script,
-                                                             fixture->lcov_output_dir);
-    g_object_unref(cache_file);
-
-    gsize coverage_data_len = 0;
-    char *coverage_data_contents =
-        eval_script_and_get_coverage_data(fixture->context,
-                                          fixture->coverage,
-                                          fixture->tmp_js_script,
-                                          fixture->lcov_output,
-                                          &coverage_data_len);
-
-    LineCountIsMoreThanData matchers[] =
-    {
-        {
-            1,
-            0
-        },
-        {
-            2,
-            0
-        }
-    };
-
-    char *script_output_path = get_output_path_for_script_on_disk(fixture->tmp_js_script,
-                                                                  fixture->lcov_output_dir);
-
-    ExpectedSourceFileCoverageData expected[] = {
-        {
-            script_output_path,
-            matchers,
-            2,
-            '2',
-            '2'
-        }
-    };
-
-    const gsize expected_len = G_N_ELEMENTS(expected);
-    const char *record = line_starting_with(coverage_data_contents, "SF:");
-    assert_coverage_data_for_source_file(expected, expected_len, record);
-
-    g_free(script_output_path);
-    g_free(coverage_data_contents);
-}
-
-static void
-unload_resource(GResource *resource)
-{
-    g_resources_unregister(resource);
-    g_resource_unref(resource);
-}
-
-static GResource *
-load_resource_from_builddir(const char *name)
-{
-    char *resource_path = g_build_filename(GJS_TOP_BUILDDIR,
-                                           name,
-                                           NULL);
-
-    GError    *error = NULL;
-    GResource *resource = g_resource_load(resource_path,
-                                          &error);
-
-    g_assert_no_error(error);
-    g_resources_register(resource);
-
-    g_free(resource_path);
-
-    return resource;
-}
-
-/* Load first resource, then unload and load second resource. Both have
- * the same path, but different contents */
-static void
-test_coverage_cache_invalidation_resource(gpointer      fixture_data,
-                                          gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-
-    GFile *mock_resource = g_file_new_for_uri("resource:///org/gnome/gjs/mock/cache/resource.js");
-
-    /* Load the resource archive and register it */
-    GResource *first_resource = load_resource_from_builddir("mock-cache-invalidation-before.gresource");
-
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script(fixture->context,
-                                                   mock_resource,
-                                                   fixture->lcov_output_dir);
-
-    GFile *cache_file = eval_file_for_tmp_ast_cache(fixture->context,
-                                                    fixture->coverage,
-                                                    mock_resource);
-
-    /* Load the "after" resource, but have the exact same coverage paths */
-    unload_resource(first_resource);
-    GResource *second_resource = load_resource_from_builddir("mock-cache-invalidation-after.gresource");
-
-    /* Overwrite tracefile with nothing */
-    replace_file(fixture->lcov_output, "");
-
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             mock_resource,
-                                                             fixture->lcov_output_dir);
-    g_object_unref(cache_file);
-
-    char *coverage_data_contents =
-        eval_script_and_get_coverage_data(fixture->context, fixture->coverage,
-                                          mock_resource, fixture->lcov_output,
-                                          NULL);
-
-    /* Don't need this anymore */
-    g_object_unref(mock_resource);
-    unload_resource(second_resource);
-
-    /* Now assert that the coverage file has executable lines in
-     * the places that we expect them to be */
-    LineCountIsMoreThanData matchers[] = {
-        {
-            1,
-            0
-        },
-        {
-            2,
-            0
-        }
-    };
-
-    GFile *output_script =
-        g_file_resolve_relative_path(fixture->lcov_output_dir,
-                                     "org/gnome/gjs/mock/cache/resource.js");
-    char *script_output_path = g_file_get_path(output_script);
-    g_object_unref(output_script);
-
-    ExpectedSourceFileCoverageData expected[] = {
-        {
-            script_output_path,
-            matchers,
-            2,
-            '2',
-            '2'
-        }
-    };
-
-    const gsize expected_len = G_N_ELEMENTS(expected);
-    const char *record = line_starting_with(coverage_data_contents, "SF:");
-    assert_coverage_data_for_source_file(expected, expected_len, record);
-
-    g_free(script_output_path);
-    g_free(coverage_data_contents);
-}
-
-static void
-test_coverage_cache_file_written_when_no_cache_exists(gpointer      fixture_data,
-                                                      gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-    GFile *cache_file = get_coverage_tmp_cache();
-
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             fixture->tmp_js_script,
-                                                             fixture->lcov_output_dir);
-
-    /* We need to execute the script now in order for a cache entry
-     * to be created, since unexecuted scripts are not counted as
-     * part of the coverage report. */
-    bool success = eval_script(fixture->context, fixture->tmp_js_script);
-    g_assert_true(success);
-
-    gjs_coverage_write_statistics(fixture->coverage);
-
-    g_assert_true(g_file_query_exists(cache_file, NULL));
-    g_object_unref(cache_file);
-}
-
-static GTimeVal
-eval_script_for_cache_mtime(GjsContext  *context,
-                            GjsCoverage *coverage,
-                            GFile       *cache_file,
-                            GFile       *script)
-{
-    bool success = eval_script(context, script);
-    g_assert_true(success);
-
-    gjs_coverage_write_statistics(coverage);
-
-    GTimeVal mtime;
-    bool successfully_got_mtime = gjs_get_file_mtime(cache_file, &mtime);
-    g_assert_true(successfully_got_mtime);
-
-    return mtime;
-}
-
-static void
-test_coverage_cache_updated_when_cache_stale(gpointer      fixture_data,
-                                             gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-
-    GFile *cache_file = get_coverage_tmp_cache();
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             fixture->tmp_js_script,
-                                                             fixture->lcov_output_dir);
-
-    GTimeVal first_cache_mtime = eval_script_for_cache_mtime(fixture->context,
-                                                             fixture->coverage,
-                                                             cache_file,
-                                                             fixture->tmp_js_script);
-
-    /* Sleep for a little while to make sure that the new file has a
-     * different mtime */
-    sleep(1);
-
-    /* Write a new script into the temporary js file, which will be
-     * completely different to the original script that was there */
-    replace_file(fixture->tmp_js_script,
-                 "let i = 0;\n"
-                 "let j = 0;\n");
-
-    /* Re-create coverage object, covering new script */
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             fixture->tmp_js_script,
-                                                             fixture->lcov_output_dir);
-
-
-    /* Run the script again, which will cause an attempt
-     * to look up the AST data. Upon writing the statistics
-     * again, the cache should have been missed some of the time
-     * so the second mtime will be greater than the first */
-    GTimeVal second_cache_mtime = eval_script_for_cache_mtime(fixture->context,
-                                                              fixture->coverage,
-                                                              cache_file,
-                                                              fixture->tmp_js_script);
-
-
-    const bool seconds_different = (first_cache_mtime.tv_sec != second_cache_mtime.tv_sec);
-    const bool microseconds_different = (first_cache_mtime.tv_usec != second_cache_mtime.tv_usec);
-
-    g_assert_true(seconds_different || microseconds_different);
-
-    g_object_unref(cache_file);
-}
-
-static void
-test_coverage_cache_not_updated_on_full_hits(gpointer      fixture_data,
-                                             gconstpointer user_data)
-{
-    GjsCoverageFixture *fixture = (GjsCoverageFixture *) fixture_data;
-
-    GFile *cache_file = get_coverage_tmp_cache();
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             fixture->tmp_js_script,
-                                                             fixture->lcov_output_dir);
-
-    GTimeVal first_cache_mtime = eval_script_for_cache_mtime(fixture->context,
-                                                             fixture->coverage,
-                                                             cache_file,
-                                                             fixture->tmp_js_script);
-
-    /* Re-create coverage object, covering same script */
-    g_clear_object(&fixture->coverage);
-    fixture->coverage = create_coverage_for_script_and_cache(fixture->context,
-                                                             cache_file,
-                                                             fixture->tmp_js_script,
-                                                             fixture->lcov_output_dir);
-
-
-    /* Run the script again, which will cause an attempt
-     * to look up the AST data. Upon writing the statistics
-     * again, the cache should have been hit of the time
-     * so the second mtime will be the same as the first */
-    GTimeVal second_cache_mtime = eval_script_for_cache_mtime(fixture->context,
-                                                              fixture->coverage,
-                                                              cache_file,
-                                                              fixture->tmp_js_script);
-
-    g_assert_cmpint(first_cache_mtime.tv_sec, ==, second_cache_mtime.tv_sec);
-    g_assert_cmpint(first_cache_mtime.tv_usec, ==, second_cache_mtime.tv_usec);
-
-    g_object_unref(cache_file);
-}
-
 typedef struct _FixturedTest {
     gsize            fixture_size;
     GTestFixtureFunc set_up;
@@ -2052,41 +1344,6 @@ add_test_for_fixture(const char      *name,
                       fixture->tear_down);
 }
 
-/* All table driven tests must be binary compatible with at
- * least this header */
-typedef struct _TestTableDataHeader {
-    const char *test_name;
-} TestTableDataHeader;
-
-static void
-add_table_driven_test_for_fixture(const char                *name,
-                                  FixturedTest              *fixture,
-                                  GTestFixtureFunc          test_func,
-                                  gsize                     table_entry_size,
-                                  gsize                     n_table_entries,
-                                  const TestTableDataHeader *test_table)
-{
-    const char  *test_table_ptr = (const char *)test_table;
-    gsize test_table_index;
-
-    for (test_table_index = 0;
-         test_table_index < n_table_entries;
-         ++test_table_index, test_table_ptr += table_entry_size) {
-        const TestTableDataHeader *header =
-            reinterpret_cast<const TestTableDataHeader *>(test_table_ptr);
-        gchar *test_name_for_table_index = g_strdup_printf("%s/%s",
-                                                           name,
-                                                           header->test_name);
-        g_test_add_vtable(test_name_for_table_index,
-                          fixture->fixture_size,
-                          test_table_ptr,
-                          fixture->set_up,
-                          test_func,
-                          fixture->tear_down);
-        g_free(test_name_for_table_index);
-    }
-}
-
 void gjs_test_add_tests_for_coverage()
 {
     FixturedTest coverage_fixture = {
@@ -2194,138 +1451,4 @@ void gjs_test_add_tests_for_coverage()
                          &coverage_for_multiple_files_to_single_output_fixture,
                          test_correct_line_coverage_data_written_for_both_source_file_sectons,
                          NULL);
-
-    /* This must be static, because g_test_add_vtable does not copy it */
-    static GjsCoverageCacheObjectNotationTableTestData data_in_expected_format_table[] = {
-        {
-            "simple_executable_lines",
-            "let i = 0;\n",
-            "resource://org/gnome/gjs/mock/test/gjs-test-coverage/cache_notation/simple_executable_lines.js",
-            "1",
-            "",
-            ""
-        },
-        {
-            "simple_branch",
-            "let i = 0;\n"
-            "if (i) {\n"
-            "    i = 1;\n"
-            "} else {\n"
-            "    i = 2;\n"
-            "}\n",
-            "resource://org/gnome/gjs/mock/test/gjs-test-coverage/cache_notation/simple_branch.js",
-            "1,2,3,5",
-            "\"point\":2,\"exits\":[3,5]",
-            ""
-        },
-        {
-            "simple_function",
-            "function f() {\n"
-            "}\n",
-            "resource://org/gnome/gjs/mock/test/gjs-test-coverage/cache_notation/simple_function.js",
-            "1,2",
-            "",
-            "\"key\":\"f:1:0\",\"line\":1"
-        }
-    };
-
-    add_table_driven_test_for_fixture("/gjs/coverage/cache/data_format",
-                                      &coverage_fixture,
-                                      test_coverage_cache_data_in_expected_format,
-                                      sizeof(GjsCoverageCacheObjectNotationTableTestData),
-                                      G_N_ELEMENTS(data_in_expected_format_table),
-                                      (const TestTableDataHeader *) data_in_expected_format_table);
-
-    add_table_driven_test_for_fixture("/gjs/coverage/cache/data_format_resource",
-                                      &coverage_fixture,
-                                      test_coverage_cache_data_in_expected_format_resource,
-                                      sizeof(GjsCoverageCacheObjectNotationTableTestData),
-                                      G_N_ELEMENTS(data_in_expected_format_table),
-                                      (const TestTableDataHeader *) data_in_expected_format_table);
-
-    static GjsCoverageCacheJSObjectTableTestData object_has_expected_properties_table[] = {
-        {
-            "simple_executable_lines",
-            "let i = 0;\n",
-            "assertArrayEquals(JSON.parse(coverage_cache)[covered_script_filename].lines, [1]);\n"
-        },
-        {
-            "simple_branch",
-            "let i = 0;\n"
-            "if (i) {\n"
-            "    i = 1;\n"
-            "} else {\n"
-            "    i = 2;\n"
-            "}\n",
-            "assertEquals(2, JSON.parse(coverage_cache)[covered_script_filename].branches[0].point);\n"
-            "assertArrayEquals([3, 5], 
JSON.parse(coverage_cache)[covered_script_filename].branches[0].exits);\n"
-        },
-        {
-            "simple_function",
-            "function f() {\n"
-            "}\n",
-            "assertEquals('f:1:0', JSON.parse(coverage_cache)[covered_script_filename].functions[0].key);\n"
-        }
-    };
-
-    add_table_driven_test_for_fixture("/gjs/coverage/cache/object_props",
-                                      &coverage_fixture,
-                                      test_coverage_cache_as_js_object_has_expected_properties,
-                                      sizeof(GjsCoverageCacheJSObjectTableTestData),
-                                      G_N_ELEMENTS(object_has_expected_properties_table),
-                                      (const TestTableDataHeader *) object_has_expected_properties_table);
-
-    static GjsCoverageCacheEqualResultsTableTestData equal_results_table[] = {
-        {
-            "simple_executable_lines",
-            "let i = 0;\n"
-            "let j = 1;\n"
-        },
-        {
-            "simple_branch",
-            "let i = 0;\n"
-            "if (i) {\n"
-            "    i = 1;\n"
-            "} else {\n"
-            "    i = 2;\n"
-            "}\n"
-        },
-        {
-            "simple_function",
-            "function f() {\n"
-            "}\n"
-        }
-    };
-
-    add_table_driven_test_for_fixture("/gjs/coverage/cache/equal/executable_lines",
-                                      &coverage_fixture,
-                                      test_coverage_cache_equal_results_to_reflect_parse,
-                                      sizeof(GjsCoverageCacheEqualResultsTableTestData),
-                                      G_N_ELEMENTS(equal_results_table),
-                                      (const TestTableDataHeader *) equal_results_table);
-
-    add_test_for_fixture("/gjs/coverage/cache/invalidation",
-                         &coverage_fixture,
-                         test_coverage_cache_invalidation,
-                         NULL);
-
-    add_test_for_fixture("/gjs/coverage/cache/invalidation_resource",
-                         &coverage_fixture,
-                         test_coverage_cache_invalidation_resource,
-                         NULL);
-
-    add_test_for_fixture("/gjs/coverage/cache/file_written",
-                         &coverage_fixture,
-                         test_coverage_cache_file_written_when_no_cache_exists,
-                         NULL);
-
-    add_test_for_fixture("/gjs/coverage/cache/no_update_on_full_hits",
-                         &coverage_fixture,
-                         test_coverage_cache_not_updated_on_full_hits,
-                         NULL);
-
-    add_test_for_fixture("/gjs/coverage/cache/update_on_misses",
-                         &coverage_fixture,
-                         test_coverage_cache_updated_when_cache_stale,
-                         NULL);
 }
diff --git a/test/mock-js-resources.gresource.xml b/test/mock-js-resources.gresource.xml
index 5585f91..196f639 100644
--- a/test/mock-js-resources.gresource.xml
+++ b/test/mock-js-resources.gresource.xml
@@ -2,8 +2,5 @@
 <gresources>
   <gresource prefix="/org/gnome/gjs/mock">
     <file>test/gjs-test-coverage/loadedJSFromResource.js</file>
-    <file>test/gjs-test-coverage/cache_notation/simple_executable_lines.js</file>
-    <file>test/gjs-test-coverage/cache_notation/simple_branch.js</file>
-    <file>test/gjs-test-coverage/cache_notation/simple_function.js</file>
   </gresource>
 </gresources>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]