diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..efb6fccad --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,21 @@ +codecov: + branch: main + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "85...100" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,files,footer" + behavior: default + require_changes: no diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 5563d1a0b..23194dcd1 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,9 +21,8 @@ jobs: - name: Build Debug Build run: cmake --build cmake-build-debug - name: Run Tests - run: | - cd cmake-build-debug/src/tests/ - ./omega_test -d yes --order lex + working-directory: cmake-build-debug/src/tests/ + run: ./omega_test -d yes --order lex - name: Create Node v10 Virtual Environment run: nodeenv --node=10.24.1 venv - name: Prepare To Build Node v10 Bindings @@ -46,17 +45,18 @@ jobs: with: fetch-depth: 0 - name: Setup Debug Build - run: cmake -S . -B cmake-build-debug + run: cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_C_FLAGS=--coverage - name: Build Debug Build run: cmake --build cmake-build-debug - name: Run Tests - run: | - cd cmake-build-debug/src/tests/ - ./omega_test -d yes --order lex + working-directory: cmake-build-debug/src/tests/ + run: ./omega_test -d yes --order lex + - name: Collect code coverage + working-directory: cmake-build-debug/src/tests/ + run: bash <(curl -s https://codecov.io/bash) - name: Run Tests Under Valgrind - run: | - cd cmake-build-debug/src/tests/ - valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./omega_test -d yes --order lex + working-directory: cmake-build-debug/src/tests/ + run: valgrind --leak-check=full --show-leak-kinds=all --leak-resolution=med --track-origins=yes --vgdb=no --error-exitcode=1 ./omega_test -d yes --order lex - name: Create Node v10 Virtual Environment run: nodeenv --node=10.24.1 venv - name: Prepare To Build Node v10 Bindings diff --git a/README.md b/README.md index c3b0c8402..0a896707e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Ωedit Library ![Build Status](https://github.com/scholarsmate/omega-edit/workflows/Unit%20Tests/badge.svg) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fscholarsmate%2Fomega-edit.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fscholarsmate%2Fomega-edit?ref=badge_shield) +[![codecov](https://codecov.io/gh/scholarsmate/omega-edit/branch/master/graph/badge.svg)](https://codecov.io/gh/scholarsmate/omega-edit) Omega Edit Logo The goal of this project is to provide an open source library for building editors that can handle massive files, multiple authors, and multiple viewports. @@ -12,7 +13,7 @@ If you are using just the command line you will need these things installed: - C/C++ compiler (such as clang, gcc, or mingw) - CMake (https://cmake.org/download/) - make or ninja -- nodeenv +- nvm or nodeenv If developing the Ωedit API, you'll need SWIG installed as well. diff --git a/src/lib/edit.cpp b/src/lib/edit.cpp index 9799dc80e..30e761e38 100644 --- a/src/lib/edit.cpp +++ b/src/lib/edit.cpp @@ -142,6 +142,24 @@ static omega_model_segment_ptr_t clone_model_segment_(const omega_model_segment_ return result; } +static inline void free_session_changes_(omega_session_t *session_ptr) { + for (auto &change_ptr : session_ptr->model_ptr_->changes) { + if (change_ptr->kind != change_kind_t::CHANGE_DELETE && 7 < change_ptr->length) { + delete[] const_cast(change_ptr.get())->data.bytes_ptr; + } + } + session_ptr->model_ptr_->changes.clear(); +} + +static inline void free_session_changes_undone_(omega_session_t *session_ptr) { + for (auto &change_ptr : session_ptr->model_ptr_->changes_undone) { + if (change_ptr->kind != change_kind_t::CHANGE_DELETE && 7 < change_ptr->length) { + delete[] const_cast(change_ptr.get())->data.bytes_ptr; + } + } + session_ptr->model_ptr_->changes_undone.clear(); +} + /* -------------------------------------------------------------------------------------------------------------------- The objective here is to model the edits using segments. Essentially creating a contiguous model of the file by keeping track of what to do. The verbs here are READ, INSERT, and OVERWRITE. We don't need to model DELETE because @@ -151,14 +169,16 @@ static omega_model_segment_ptr_t clone_model_segment_(const omega_model_segment_ static int update_model_helper_(omega_model_t *model_ptr, const_omega_change_ptr_t &change_ptr) { int64_t read_offset = 0; - if (model_ptr->model_segments.empty() && change_ptr->kind != change_kind_t::CHANGE_DELETE) { - // The model is empty, and we have a change with content - auto insert_segment_ptr = std::make_unique(); - insert_segment_ptr->computed_offset = change_ptr->offset; - insert_segment_ptr->computed_length = change_ptr->length; - insert_segment_ptr->change_offset = 0; - insert_segment_ptr->change_ptr = change_ptr; - model_ptr->model_segments.push_back(std::move(insert_segment_ptr)); + if (model_ptr->model_segments.empty()) { + if (change_ptr->kind != change_kind_t::CHANGE_DELETE) { + // The model is empty, and we have a change with content + auto insert_segment_ptr = std::make_unique(); + insert_segment_ptr->computed_offset = change_ptr->offset; + insert_segment_ptr->computed_length = change_ptr->length; + insert_segment_ptr->change_offset = 0; + insert_segment_ptr->change_ptr = change_ptr; + model_ptr->model_segments.push_back(std::move(insert_segment_ptr)); + } return 0; } for (auto iter = model_ptr->model_segments.begin(); iter != model_ptr->model_segments.end(); ++iter) { @@ -253,7 +273,7 @@ static int64_t update_(omega_session_t *session_ptr, const_omega_change_ptr_t ch undone_change_ptr->serial *= -1; } else if (!session_ptr->model_ptr_->changes_undone.empty()) { // This is not a redo change, so any changes undone are now invalid and must be cleared - session_ptr->model_ptr_->changes_undone.clear(); + free_session_changes_undone_(session_ptr); } session_ptr->model_ptr_->changes.push_back(change_ptr); if (0 != update_model_(session_ptr->model_ptr_.get(), change_ptr)) { return -1; } @@ -289,11 +309,8 @@ omega_session_t *omega_edit_create_session(const char *file_path, omega_session_ void omega_edit_destroy_session(omega_session_t *session_ptr) { if (session_ptr->file_ptr) { fclose(session_ptr->file_ptr); } while (!session_ptr->viewports_.empty()) { omega_edit_destroy_viewport(session_ptr->viewports_.back().get()); } - for (auto &change_ptr : session_ptr->model_ptr_->changes) { - if (change_ptr->kind != change_kind_t::CHANGE_DELETE && 7 < change_ptr->length) { - delete[] const_cast(change_ptr.get())->data.bytes_ptr; - } - } + free_session_changes_(session_ptr); + free_session_changes_undone_(session_ptr); delete session_ptr; } @@ -349,7 +366,7 @@ int64_t omega_edit_insert(omega_session_t *session_ptr, int64_t offset, const ch int64_t omega_edit_overwrite_bytes(omega_session_t *session_ptr, int64_t offset, const omega_byte_t *bytes, int64_t length) { - return (0 <= length && offset < omega_session_get_computed_file_size(session_ptr)) + return (0 <= length && offset <= omega_session_get_computed_file_size(session_ptr)) ? update_(session_ptr, ovr_(1 + static_cast(omega_session_get_num_changes(session_ptr)), offset, bytes, length)) : 0; @@ -405,7 +422,7 @@ int omega_edit_clear_changes(omega_session_t *session_ptr) { length = ftello(session_ptr->file_ptr); } initialize_model_segments_(session_ptr->model_ptr_->model_segments, length); - session_ptr->model_ptr_->changes.clear(); + free_session_changes_(session_ptr); for (const auto &viewport_ptr : session_ptr->viewports_) { viewport_ptr->data_segment.capacity = -1 * std::abs(viewport_ptr->data_segment.capacity);// indicate dirty read omega_viewport_execute_on_change(viewport_ptr.get(), nullptr); diff --git a/src/tests/omega_test.cpp b/src/tests/omega_test.cpp index 44f85f31e..2abaca906 100644 --- a/src/tests/omega_test.cpp +++ b/src/tests/omega_test.cpp @@ -50,11 +50,9 @@ TEST_CASE("Buffer Shift", "[BufferShift]") { auto buff_len = (int64_t) strlen((const char *) buffer); // Shift the buffer 3 bits to the right - auto rc = omega_util_right_shift_buffer(buffer, buff_len, 3); - REQUIRE(rc == 0); + REQUIRE(0 == omega_util_right_shift_buffer(buffer, buff_len, 3)); // Shift the buffer 5 bits to the right - rc = omega_util_right_shift_buffer(buffer, buff_len, 5); - REQUIRE(rc == 0); + REQUIRE(0 == omega_util_right_shift_buffer(buffer, buff_len, 5)); // We shifted a total of 8 bits (one byte) to the right, so compare the buffer against the fill plus one byte REQUIRE(strcmp((const char *) fill + 1, (const char *) buffer) == 0); @@ -63,14 +61,16 @@ TEST_CASE("Buffer Shift", "[BufferShift]") { REQUIRE(strcmp((const char *) fill, (const char *) buffer) == 0); // Shift the buffer 6 bits to the left - rc = omega_util_left_shift_buffer(buffer, buff_len, 6); - REQUIRE(rc == 0); + REQUIRE(0 == omega_util_left_shift_buffer(buffer, buff_len, 6)); // Shift the buffer 2 bits to the left - rc = omega_util_left_shift_buffer(buffer, buff_len, 2); - REQUIRE(0 == rc); + REQUIRE(0 == omega_util_left_shift_buffer(buffer, buff_len, 2)); // We shifted a total of 8 bits (one byte) to the left, so compare the buffer against the fill plus one byte REQUIRE(strcmp((const char *) fill + 1, (const char *) buffer) == 0); + // Negative tests. Shifting 8 or more bits in either direction should be an error. + REQUIRE(-1 == omega_util_left_shift_buffer(buffer, buff_len, 8)); + REQUIRE(-1 == omega_util_right_shift_buffer(buffer, buff_len, 8)); + free(buffer); } @@ -85,6 +85,17 @@ TEST_CASE("File Compare", "[UtilTests]") { } } +TEST_CASE("File Exists", "[UtilTests]") { + REQUIRE(omega_util_file_exists("data/test1.dat")); + REQUIRE(!omega_util_file_exists("data/IDonTExist.DaT")); +} + +TEST_CASE("Current Directory", "[UtilTests]") { + using Catch::Matchers::Contains; + using Catch::Matchers::EndsWith; + REQUIRE_THAT(omega_util_get_current_dir(), Contains("src") && EndsWith("tests")); +} + static inline omega_byte_t to_lower(omega_byte_t byte) { return tolower(byte); } static inline omega_byte_t to_upper(omega_byte_t byte) { return toupper(byte); } @@ -148,9 +159,16 @@ TEST_CASE("Empty File Test", "[EmptyFileTests]") { REQUIRE(session_ptr); REQUIRE(strcmp(omega_session_get_file_path(session_ptr), in_filename) == 0); REQUIRE(omega_session_get_computed_file_size(session_ptr) == file_size); - REQUIRE(0 < omega_edit_insert_bytes(session_ptr, 0, reinterpret_cast("0"), 0)); - file_size += 1; - REQUIRE(omega_session_get_computed_file_size(session_ptr) == file_size); + REQUIRE(0 == omega_edit_undo_last_change(session_ptr)); + auto change_serial = omega_edit_insert_bytes(session_ptr, 0, reinterpret_cast("1234567890"), 0); + REQUIRE(0 < change_serial); + file_size += 10; + REQUIRE(file_size == omega_session_get_computed_file_size(session_ptr)); + REQUIRE((change_serial * -1) == omega_edit_undo_last_change(session_ptr)); + REQUIRE(0 == omega_session_get_computed_file_size(session_ptr)); + change_serial = omega_edit_overwrite_string(session_ptr, 0, "abcdefghhijklmnopqrstuvwxyz"); + REQUIRE(0 < change_serial); + REQUIRE(27 == omega_session_get_computed_file_size(session_ptr)); omega_edit_destroy_session(session_ptr); }