[gjs: 3/6] coverage: Use LCOV data from SpiderMonkey
- From: Gitlab Administrative User <gitlab src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs: 3/6] coverage: Use LCOV data from SpiderMonkey
- Date: Tue, 24 Oct 2017 13:44:42 +0000 (UTC)
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]