Browse Source

Added core coverage support.

Brucey 2 years ago
parent
commit
b639f4771f

+ 5 - 0
blitz.mod/blitz.bmx

@@ -117,6 +117,11 @@ Import "blitz_ex.c"
 Import "blitz_gc.c"
 Import "blitz_unicode.c"
 Import "blitz_enum.c"
+Import "blitz_coverage.c"
+
+?coverage
+Import "hashmap/hashmap.c"
+?
 
 '?Threaded
 'Import "blitz_gc_ms.c"

+ 1 - 0
blitz.mod/blitz.h

@@ -39,6 +39,7 @@
 #include "blitz_handle.h"
 #include "blitz_app.h" 
 #include "blitz_enum.h"
+#include "blitz_coverage.h"
 
 #ifdef __cplusplus
 extern "C"{

+ 5 - 0
blitz.mod/blitz_app.c

@@ -583,6 +583,11 @@ void bbStartup( int argc,char *argv[],void *dummy1,void *dummy2 ){
 
 #endif
 
+	#ifdef BMX_COVERAGE
+		bbCoverageStartup();
+		atexit( bbCoverageGenerateOutput );
+	#endif
+
 	BBINCREFS( bbLaunchDir );
 	BBINCREFS( bbAppDir );
 	BBINCREFS( bbAppFile );

+ 181 - 0
blitz.mod/blitz_coverage.c

@@ -0,0 +1,181 @@
+
+#include "blitz.h"
+
+#ifdef BMX_COVERAGE
+
+BBString * bbCoverageOutputFileName = &bbEmptyString;
+
+Hashmap* BBCoverageLineExecInfoTable;
+
+static int hashmapStringHash(intptr_t key) {
+    const char* str = (const char*)key;
+    int64_t hash = 0;
+
+    while (*str) {
+        hash = (31 * hash + *str++) % 0x7FFFFFFF;
+    }
+
+    return (int)(hash % 0x7FFFFFFF);
+}
+
+static int hashmapStringEquals(intptr_t keyA, intptr_t keyB) {
+    const char* strA = (const char*)keyA;
+    const char* strB = (const char*)keyB;
+
+    return strcmp(strA, strB) == 0;
+}
+
+void bbCoverageStartup() {
+    BBCoverageLineExecInfoTable = hashmapCreate(64, hashmapStringHash, hashmapStringEquals);
+}
+
+void bbCoverageRegisterFile(BBCoverageFileInfo * coverage_files) {
+
+    for (int i = 0; coverage_files[i].filename != NULL; i++) {
+        BBCoverageFileInfo * coverage_file = &coverage_files[i];
+        
+        if (coverage_file->line_map == NULL) {
+            coverage_file->line_map = hashmapCreate(64, hashmapIntHash, hashmapIntEquals);
+            hashmapPut(BBCoverageLineExecInfoTable, (void*)coverage_file->filename, coverage_file);
+        }
+        
+        if (coverage_file->func_map == NULL) {
+            coverage_file->func_map = hashmapCreate(64, hashmapIntHash, hashmapIntEquals);
+        }
+ 
+        for (int j = 0; j < coverage_file->coverage_lines_count; j++) {
+            BBCoverageLineExecInfo* info = (BBCoverageLineExecInfo*) malloc(sizeof(BBCoverageLineExecInfo));
+            info->file = coverage_file->filename;
+            info->line = coverage_file->coverage_lines[j];
+            info->count = 0;
+            hashmapPut(coverage_file->line_map, (void*)(intptr_t)info->line, info);
+        }
+
+        for (int j = 0; j < coverage_file->coverage_functions_count; j++) {
+            BBCoverageFuncExecInfo* info = (BBCoverageFuncExecInfo*) malloc(sizeof(BBCoverageFuncExecInfo));
+            info->func = coverage_file->coverage_functions[j].func;
+            info->line = coverage_file->coverage_functions[j].line;
+            info->count = 0;
+            hashmapPut(coverage_file->func_map, (void*)(intptr_t)info->line, info);
+        }
+    }
+}
+
+void bbCoverageUpdateLineInfo(const char* file, int line) {
+    BBCoverageFileInfo * coverage_file = (Hashmap*) hashmapGet(BBCoverageLineExecInfoTable, (void*)file);
+    
+    if (!coverage_file) {
+        // error
+
+        return;
+    }
+
+    BBCoverageLineExecInfo* info = (BBCoverageLineExecInfo*) hashmapGet(coverage_file->line_map, (void*)(intptr_t)line);
+    
+    if (!info) {
+        info = (BBCoverageLineExecInfo*) malloc(sizeof(BBCoverageLineExecInfo));
+        info->file = file;
+        info->line = line;
+        info->count = 0;
+        hashmapPut(coverage_file->line_map, (void*)(intptr_t)line, info);
+    }
+
+    info->count++;
+}
+
+void bbCoverageUpdateFunctionLineInfo(const char* file, const char* func, int line) {
+    BBCoverageFileInfo * coverage_file = (Hashmap*) hashmapGet(BBCoverageLineExecInfoTable, (void*)file);
+    
+    if (!coverage_file) {
+        // error
+
+        return;
+    }
+
+    BBCoverageFuncExecInfo* info = (BBCoverageFuncExecInfo*) hashmapGet(coverage_file->func_map, (void*)(intptr_t)line);
+    
+    if (!info) {
+        info = (BBCoverageFuncExecInfo*) malloc(sizeof(BBCoverageFuncExecInfo));
+        info->file = file;
+        info->func = func;
+        info->line = line;
+        info->count = 0;
+        hashmapPut(coverage_file->line_map, (void*)(intptr_t)line, info);
+    }
+
+    info->count++;
+}
+
+static int write_line_exec_info(intptr_t key, void* value, void* context) {
+    BBCoverageLineExecInfo* info = (BBCoverageLineExecInfo*)value;
+    FILE* lcov_file = (FILE*)context;
+    fprintf(lcov_file, "DA:%d,%d\n", info->line, info->count);
+    return 1;
+}
+
+static int write_file_exec_info(intptr_t key, void* value, void* context) {
+    const char* file = (const char*)key;
+    BBCoverageFileInfo* coverage_file = (BBCoverageFileInfo*)value;
+    FILE* lcov_file = (FILE*)context;
+
+    fprintf(lcov_file, "SF:%s\n", file);
+
+    int lines_hit = 0;
+    for (int i=0; i < coverage_file->coverage_lines_count; i++) {
+        BBCoverageLineExecInfo* info = (BBCoverageLineExecInfo*) hashmapGet(coverage_file->line_map, (void*)(intptr_t)coverage_file->coverage_lines[i]);
+        if (info) {
+            if (info->count > 0) {
+                lines_hit++;
+            }
+            fprintf(lcov_file, "DA:%d,%d\n", info->line, info->count);
+        }
+    }
+
+    fprintf(lcov_file, "LF:%d\n", coverage_file->coverage_lines_count);
+    fprintf(lcov_file, "LH:%d\n", lines_hit);
+
+    int functions_hit = 0;
+    for (int i=0; i < coverage_file->coverage_functions_count; i++) {
+        BBCoverageFuncExecInfo* info = (BBCoverageFuncExecInfo*) hashmapGet(coverage_file->func_map, (void*)(intptr_t)coverage_file->coverage_functions[i].line);
+        if (info) {
+            if (info->count > 0) {
+                functions_hit++;
+            }
+            fprintf(lcov_file, "FN:%d,%s\n", info->line, info->func);
+            fprintf(lcov_file, "FNDA:%d,%d\n", info->count, info->line);
+        }
+    }
+
+    fprintf(lcov_file, "FNF:%d\n", coverage_file->coverage_functions_count);
+    fprintf(lcov_file, "FNH:%d\n", functions_hit);
+
+    // Write the end of record marker
+    fprintf(lcov_file, "end_of_record\n");
+
+    return 1;
+}
+
+void bbCoverageGenerateOutput() {
+
+    const char* output_file_name;
+
+    if ( bbCoverageOutputFileName == &bbEmptyString ) {
+        output_file_name = "lcov.info";
+    } else {
+        output_file_name = bbStringToUTF8String(bbCoverageOutputFileName);
+    }
+    
+    FILE* lcov_file = fopen(output_file_name, "w");
+    if (!lcov_file) {
+        printf("Error: Unable to open output file: %s\n", output_file_name);
+        return;
+    }
+
+    // Iterate through the global hash table (BBCoverageLineExecInfoTable) and write file coverage data
+    hashmapForEach(BBCoverageLineExecInfoTable, write_file_exec_info, lcov_file);
+
+    fclose(lcov_file);
+}
+
+
+#endif // BMX_COVERAGE

+ 46 - 0
blitz.mod/blitz_coverage.h

@@ -0,0 +1,46 @@
+#ifndef BLITZ_COVERAGE_H
+#define BLITZ_COVERAGE_H
+
+#ifdef BMX_COVERAGE
+
+#include "hashmap/hashmap.h"
+
+typedef struct {
+    const char* file;
+    int line;
+    int count;
+} BBCoverageLineExecInfo;
+
+typedef struct {
+    const char* file;
+    const char* func;
+    int line;
+    int count;
+} BBCoverageFuncExecInfo;
+
+typedef struct {
+    const char* func;
+    int line;
+} BBCoverageFunctionInfo;
+
+typedef struct {
+    const char* filename;
+    const int* coverage_lines;
+    size_t coverage_lines_count;
+    void* line_map;
+    const BBCoverageFunctionInfo* coverage_functions;
+    size_t coverage_functions_count;
+    void* func_map;
+} BBCoverageFileInfo;
+
+extern BBString * bbCoverageOutputFileName;
+
+void bbCoverageStartup();
+void bbCoverageRegisterFile(BBCoverageFileInfo * coverage_files);
+void bbCoverageUpdateLineInfo(const char* file, int line);
+void bbCoverageUpdateFunctionLineInfo(const char* file, const char* func, int line);
+void bbCoverageGenerateOutput();
+
+#endif // BMX_COVERAGE
+
+#endif // BLITZ_COVERAGE_H

+ 353 - 0
blitz.mod/hashmap/hashmap.c

@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "blitz.h"
+#include "hashmap.h"
+#include <assert.h>
+#include <errno.h>
+// #include "threads.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <stdint.h>
+
+typedef struct Entry Entry;
+struct Entry {
+    intptr_t key;
+    int hash;
+    void* value;
+    Entry* next;
+};
+
+struct Hashmap {
+    Entry** buckets;
+    size_t bucketCount;
+    int (*hash)(intptr_t key);
+    bool (*equals)(intptr_t keyA, intptr_t keyB);
+    bb_mutex_t lock; 
+    size_t size;
+};
+
+Hashmap* hashmapCreate(size_t initialCapacity,
+        int (*hash)(intptr_t key), int (*equals)(intptr_t keyA, intptr_t keyB)) {
+    assert(hash != NULL);
+    assert(equals != NULL);
+    
+    Hashmap* map = malloc(sizeof(Hashmap));
+    if (map == NULL) {
+        return NULL;
+    }
+    
+    // 0.75 load factor.
+    size_t minimumBucketCount = initialCapacity * 4 / 3;
+    map->bucketCount = 1;
+    while (map->bucketCount <= minimumBucketCount) {
+        // Bucket count must be power of 2.
+        map->bucketCount <<= 1; 
+    }
+
+    map->buckets = calloc(map->bucketCount, sizeof(Entry*));
+    if (map->buckets == NULL) {
+        free(map);
+        return NULL;
+    }
+    
+    map->size = 0;
+
+    map->hash = hash;
+    map->equals = equals;
+    
+    bb_mutex_init(&map->lock);
+    
+    return map;
+}
+
+/**
+ * Hashes the given key.
+ */
+static inline int hashKey(Hashmap* map, intptr_t key) {
+    int h = map->hash(key);
+
+    // We apply this secondary hashing discovered by Doug Lea to defend
+    // against bad hashes.
+    h += ~(h << 9);
+    h ^= (((unsigned int) h) >> 14);
+    h += (h << 4);
+    h ^= (((unsigned int) h) >> 10);
+       
+    return h;
+}
+
+size_t hashmapSize(Hashmap* map) {
+    return map->size;
+}
+
+static inline size_t calculateIndex(size_t bucketCount, int hash) {
+    return ((size_t) hash) & (bucketCount - 1);
+}
+
+static void expandIfNecessary(Hashmap* map) {
+    // If the load factor exceeds 0.75...
+    if (map->size > (map->bucketCount * 3 / 4)) {
+        // Start off with a 0.33 load factor.
+        size_t newBucketCount = map->bucketCount << 1;
+        Entry** newBuckets = calloc(newBucketCount, sizeof(Entry*));
+        if (newBuckets == NULL) {
+            // Abort expansion.
+            return;
+        }
+        
+        // Move over existing entries.
+        size_t i;
+        for (i = 0; i < map->bucketCount; i++) {
+            Entry* entry = map->buckets[i];
+            while (entry != NULL) {
+                Entry* next = entry->next;
+                size_t index = calculateIndex(newBucketCount, entry->hash);
+                entry->next = newBuckets[index];
+                newBuckets[index] = entry;
+                entry = next;
+            }
+        }
+
+        // Copy over internals.
+        free(map->buckets);
+        map->buckets = newBuckets;
+        map->bucketCount = newBucketCount;
+    }
+}
+
+void hashmapLock(Hashmap* map) {
+    bb_mutex_lock(&map->lock);
+}
+
+void hashmapUnlock(Hashmap* map) {
+    bb_mutex_unlock(&map->lock);
+}
+
+void hashmapFree(Hashmap* map) {
+    size_t i;
+    for (i = 0; i < map->bucketCount; i++) {
+        Entry* entry = map->buckets[i];
+        while (entry != NULL) {
+            Entry* next = entry->next;
+            free(entry);
+            entry = next;
+        }
+    }
+    free(map->buckets);
+    bb_mutex_destroy(&map->lock);
+    free(map);
+}
+
+int hashmapHash(intptr_t key, size_t keySize) {
+    int64_t h = keySize;
+    char* data = (char*) key;
+    size_t i;
+    for (i = 0; i < keySize; i++) {
+        h = (h * 31 + *data) % 0x7FFFFFFF;
+        data++;
+    }
+    return (int)(h % 0x7FFFFFFF);
+}
+
+static Entry* createEntry(intptr_t key, int hash, void* value) {
+    Entry* entry = malloc(sizeof(Entry));
+    if (entry == NULL) {
+        return NULL;
+    }
+    entry->key = key;
+    entry->hash = hash;
+    entry->value = value;
+    entry->next = NULL;
+    return entry;
+}
+
+static inline int equalKeys(intptr_t keyA, int hashA, intptr_t keyB, int hashB,
+        int (*equals)(intptr_t, intptr_t)) {
+    if (keyA == keyB) {
+        return 1;
+    }
+    if (hashA != hashB) {
+        return 0;
+    }
+    return equals(keyA, keyB);
+}
+
+void* hashmapPut(Hashmap* map, intptr_t key, void* value) {
+    int hash = hashKey(map, key);
+    size_t index = calculateIndex(map->bucketCount, hash);
+
+    Entry** p = &(map->buckets[index]);
+    while (true) {
+        Entry* current = *p;
+
+        // Add a new entry.
+        if (current == NULL) {
+            *p = createEntry(key, hash, value);
+            if (*p == NULL) {
+                errno = ENOMEM;
+                return NULL;
+            }
+            map->size++;
+            expandIfNecessary(map);
+            return NULL;
+        }
+
+        // Replace existing entry.
+        if (equalKeys(current->key, current->hash, key, hash, map->equals)) {
+            void* oldValue = current->value;
+            current->value = value;
+            return oldValue;
+        }
+
+        // Move to next entry.
+        p = &current->next;
+    }
+}
+
+void* hashmapGet(Hashmap* map, intptr_t key) {
+    int hash = hashKey(map, key);
+    size_t index = calculateIndex(map->bucketCount, hash);
+
+    Entry* entry = map->buckets[index];
+    while (entry != NULL) {
+        if (equalKeys(entry->key, entry->hash, key, hash, map->equals)) {
+            return entry->value;
+        }
+        entry = entry->next;
+    }
+
+    return NULL;
+}
+
+int hashmapContainsKey(Hashmap* map, intptr_t key) {
+    int hash = hashKey(map, key);
+    size_t index = calculateIndex(map->bucketCount, hash);
+
+    Entry* entry = map->buckets[index];
+    while (entry != NULL) {
+        if (equalKeys(entry->key, entry->hash, key, hash, map->equals)) {
+            return 1;
+        }
+        entry = entry->next;
+    }
+
+    return 0;
+}
+
+void* hashmapMemoize(Hashmap* map, intptr_t key, 
+        void* (*initialValue)(intptr_t key, void* context), void* context) {
+    int hash = hashKey(map, key);
+    size_t index = calculateIndex(map->bucketCount, hash);
+
+    Entry** p = &(map->buckets[index]);
+    while (true) {
+        Entry* current = *p;
+
+        // Add a new entry.
+        if (current == NULL) {
+            *p = createEntry(key, hash, NULL);
+            if (*p == NULL) {
+                errno = ENOMEM;
+                return NULL;
+            }
+            void* value = initialValue(key, context);
+            (*p)->value = value;
+            map->size++;
+            expandIfNecessary(map);
+            return value;
+        }
+
+        // Return existing value.
+        if (equalKeys(current->key, current->hash, key, hash, map->equals)) {
+            return current->value;
+        }
+
+        // Move to next entry.
+        p = &current->next;
+    }
+}
+
+void* hashmapRemove(Hashmap* map, intptr_t key) {
+    int hash = hashKey(map, key);
+    size_t index = calculateIndex(map->bucketCount, hash);
+
+    // Pointer to the current entry.
+    Entry** p = &(map->buckets[index]);
+    Entry* current;
+    while ((current = *p) != NULL) {
+        if (equalKeys(current->key, current->hash, key, hash, map->equals)) {
+            void* value = current->value;
+            *p = current->next;
+            free(current);
+            map->size--;
+            return value;
+        }
+
+        p = &current->next;
+    }
+
+    return NULL;
+}
+
+void hashmapForEach(Hashmap* map, 
+        bool (*callback)(intptr_t key, void* value, void* context),
+        void* context) {
+    size_t i;
+    for (i = 0; i < map->bucketCount; i++) {
+        Entry* entry = map->buckets[i];
+        while (entry != NULL) {
+            Entry *next = entry->next;
+            if (!callback(entry->key, entry->value, context)) {
+                return;
+            }
+            entry = next;
+        }
+    }
+}
+
+size_t hashmapCurrentCapacity(Hashmap* map) {
+    size_t bucketCount = map->bucketCount;
+    return bucketCount * 3 / 4;
+}
+
+size_t hashmapCountCollisions(Hashmap* map) {
+    size_t collisions = 0;
+    size_t i;
+    for (i = 0; i < map->bucketCount; i++) {
+        Entry* entry = map->buckets[i];
+        while (entry != NULL) {
+            if (entry->next != NULL) {
+                collisions++;
+            }
+            entry = entry->next;
+        }
+    }
+    return collisions;
+}
+
+int hashmapIntHash(intptr_t key) {
+    // Return the key value itself.
+    return (int)key;
+}
+
+int hashmapIntEquals(intptr_t keyA, intptr_t keyB) {
+    // int a = *((int*) keyA);
+    // int b = *((int*) keyB);
+    return keyA == keyB;
+}

+ 150 - 0
blitz.mod/hashmap/hashmap.h

@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Hash map.
+ */
+
+#ifndef __HASHMAP_H
+#define __HASHMAP_H
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** A hash map. */
+typedef struct Hashmap Hashmap;
+
+/**
+ * Creates a new hash map. Returns NULL if memory allocation fails.
+ *
+ * @param initialCapacity number of expected entries
+ * @param hash function which hashes keys
+ * @param equals function which compares keys for equality
+ */
+Hashmap* hashmapCreate(size_t initialCapacity,
+        int (*hash)(intptr_t key), int (*equals)(intptr_t keyA, intptr_t keyB));
+
+/**
+ * Frees the hash map. Does not free the keys or values themselves.
+ */
+void hashmapFree(Hashmap* map);
+
+/**
+ * Hashes the memory pointed to by key with the given size. Useful for
+ * implementing hash functions.
+ */
+int hashmapHash(intptr_t key, size_t keySize);
+
+/**
+ * Puts value for the given key in the map. Returns pre-existing value if
+ * any.
+ *
+ * If memory allocation fails, this function returns NULL, the map's size
+ * does not increase, and errno is set to ENOMEM.
+ */
+void* hashmapPut(Hashmap* map, intptr_t key, void* value);
+
+/**
+ * Gets a value from the map. Returns NULL if no entry for the given key is
+ * found or if the value itself is NULL.
+ */
+void* hashmapGet(Hashmap* map, intptr_t key);
+
+/**
+ * Returns true if the map contains an entry for the given key.
+ */
+int hashmapContainsKey(Hashmap* map, intptr_t key);
+
+/**
+ * Gets the value for a key. If a value is not found, this function gets a 
+ * value and creates an entry using the given callback.
+ *
+ * If memory allocation fails, the callback is not called, this function
+ * returns NULL, and errno is set to ENOMEM.
+ */
+void* hashmapMemoize(Hashmap* map, intptr_t key, 
+        void* (*initialValue)(intptr_t key, void* context), void* context);
+
+/**
+ * Removes an entry from the map. Returns the removed value or NULL if no
+ * entry was present.
+ */
+void* hashmapRemove(Hashmap* map, intptr_t key);
+
+/**
+ * Gets the number of entries in this map.
+ */
+size_t hashmapSize(Hashmap* map);
+
+/**
+ * Invokes the given callback on each entry in the map. Stops iterating if
+ * the callback returns false.
+ */
+void hashmapForEach(Hashmap* map, 
+        bool (*callback)(intptr_t key, void* value, void* context),
+        void* context);
+
+/**
+ * Concurrency support.
+ */
+
+/**
+ * Locks the hash map so only the current thread can access it.
+ */
+void hashmapLock(Hashmap* map);
+
+/**
+ * Unlocks the hash map so other threads can access it.
+ */
+void hashmapUnlock(Hashmap* map);
+
+/**
+ * Key utilities.
+ */
+
+/**
+ * Hashes int keys. 'key' is a pointer to int.
+ */
+int hashmapIntHash(intptr_t key);
+
+/**
+ * Compares two int keys for equality.
+ */
+int hashmapIntEquals(intptr_t keyA, intptr_t keyB);
+
+/**
+ * For debugging.
+ */
+
+/**
+ * Gets current capacity.
+ */
+size_t hashmapCurrentCapacity(Hashmap* map);
+
+/**
+ * Counts the number of entry collisions.
+ */
+size_t hashmapCountCollisions(Hashmap* map);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __HASHMAP_H */