From c75b218dbb6f08d857d868abae28e2fb1dba7aa6 Mon Sep 17 00:00:00 2001 From: Fraxy V Date: Sun, 1 Sep 2024 11:16:44 +0300 Subject: [PATCH] test --- .github/workflows/c-cpp.yml | 161 ++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 18 +++- coverage.cmake | 37 +++++++++ scripts/diff.js | 36 ++++++++ scripts/targets.js | 45 ++++++++++ src/my_static_lib.cpp | 5 +- src/test1.cpp | 9 ++ src/test2.cpp | 9 ++ 8 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/c-cpp.yml create mode 100644 coverage.cmake create mode 100644 scripts/diff.js create mode 100644 scripts/targets.js create mode 100644 src/test1.cpp create mode 100644 src/test2.cpp diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml new file mode 100644 index 0000000..2e53855 --- /dev/null +++ b/.github/workflows/c-cpp.yml @@ -0,0 +1,161 @@ +name: Build +on: + push: +jobs: + build-project: + name: Build Project + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v4.1.7 + + - name: Fetch master + run: | + git fetch --no-tags --prune --depth=1 origin master + + - name: Setup Ninja + uses: seanmiddleditch/gha-setup-ninja@v5 + + - name: Build Project + uses: threeal/cmake-action@v2.0.0 + with: + generator: Ninja + options: | + CMAKE_EXPORT_COMPILE_COMMANDS=ON + ENABLE_COVERAGE=ON + run-build: false + + - name: Get modified source + id: get-modified-source + uses: actions/github-script@v7 + with: + script: | + const diff = require('./scripts/diff.js'); + console.log(diff()) + + - name: Get affected targets + id: find-targets + uses: actions/github-script@v7 + with: + result-encoding: string + script: | + const fs = require('fs'); + const { execSync } = require('child_process'); + + let sources = execSync(`git diff --name-only origin/master`).toString().split('\n') + let outputs = []; + let commands = JSON.parse(fs.readFileSync('build/compile_commands.json', 'utf8')); + for (let src of sources) { + if (src.endsWith('.cpp') || src.endsWith('.h') || src.endsWith('.c') + || src.endsWith('.hpp')) { + const idx = commands.findIndex(entry => entry.file.endsWith(src)); + if (idx >= 0) { + let entry = commands[idx]; + if (entry.output !== 'undefined') { + entry.output = entry.command.split(' ').find(token => token.endsWith('.o')); + } + outputs.push(entry.output); + commands.splice(idx, 1); + } + } + } + + targets = new Set(); + while (outputs.length > 0) { + level_targets = new Set(); + for (const output of outputs) { + let lines = execSync(`ninja -C build -t query ${output}`).toString().split('\n'); + + let insert = false; + for (const line of lines) { + if (line.endsWith('outputs:')) { + insert = true; + continue; + } + const value = line.trim(); + if (insert && value.length > 0) { + level_targets.add(value); + } + } + } + + outputs = [...level_targets]; + targets = new Set([...targets, ...level_targets]); + } + return [...targets].join(' '); + + - name: Build affected targets + if: ${{steps.find-targets.outputs.result != ''}} + run: ninja -C build ${{steps.find-targets.outputs.result}} + + - name: Run ctest + if: ${{steps.find-targets.outputs.result != ''}} + run: ctest --test-dir build + + - name: Generate a code coverage report + if: ${{steps.find-targets.outputs.result != ''}} + uses: threeal/gcovr-action@xml-out + with: + xml-out: coverage.xml + + - run: npm install xml2js + + - name: Check coverage report + id: check-coverage + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const parseString = require('xml2js').parseString; + const result = await new Promise((resolve, reject) => parseString(fs.readFileSync('coverage.xml', 'utf8'), (err, result) => { + if (err) reject(err); + else resolve(result); + })); + console.log('${{ github.base_ref }}'); + console.log('${{ github.head_ref }}'); + console.log('${{ github.ref }}'); + console.log('${{ github.ref_name }}'); + console.log('${{ github.path }}'); + + + # "packages": [ + # { + # "package": [ + # { + # "$": { + # "name": "src", + # "line-rate": "0.5", + # "branch-rate": "0.5", + # "complexity": "0.0" + # }, + # "classes": [ + # { + # "class": [ + # { + # "$": { + # "name": "main_cpp", + # "filename": "src/main.cpp", + # "line-rate": "0.0", + # "branch-rate": "1.0", + # "complexity": "0.0" + # }, + # "methods": [ + # "" + # ], + # "lines": [ + # { + # "line": [ + # { + # "$": { + # "number": "6", + # "hits": "0", + # "branch": "false" + # } + # }, + # { + # "$": { + # "number": "7", + # "hits": "0", + # "branch": "false" + # } + # }, diff --git a/CMakeLists.txt b/CMakeLists.txt index 69da88d..9731cc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,25 @@ -project(coverage_action) +cmake_minimum_required(VERSION 3.22) +project(coverage_action) +include(CTest) add_library(my_static_lib STATIC src/my_static_lib.cpp) add_library(my_shared_lib SHARED src/my_shared_lib.cpp) +if (BUILD_TESTING) + enable_testing() + include(coverage.cmake) + add_executable(test_shared_lib src/test1.cpp) + target_link_libraries(test_shared_lib my_shared_lib) + + add_executable(test_static_lib src/test2.cpp) + target_link_libraries(test_static_lib my_static_lib) + + add_test(NAME test_shared_lib COMMAND test_shared_lib) + add_test(NAME test_static_lib COMMAND test_static_lib) +endif() + + add_executable(main src/main.cpp) target_link_libraries(main my_static_lib my_shared_lib) \ No newline at end of file diff --git a/coverage.cmake b/coverage.cmake new file mode 100644 index 0000000..9aaf37c --- /dev/null +++ b/coverage.cmake @@ -0,0 +1,37 @@ +#========================================================================= +# +# This software is distributed WITHOUT ANY WARRANTY; without even +# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the above copyright notice for more information. +# +#========================================================================= + +# This code has been adapted from remus (https://gitlab.kitware.com/cmb/remus) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR + CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + set(CMAKE_COMPILER_IS_CLANGXX 1) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + + include(CheckCXXCompilerFlag) + + #Add option for enabling gcov coverage + option(ENABLE_COVERAGE "Build with gcov support." OFF) + mark_as_advanced(ENABLE_COVERAGE) + + if(ENABLE_COVERAGE) + #We're setting the CXX flags and C flags beacuse they're propagated down + #independent of build type. + if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -fno-elide-constructors -fprofile-instr-generate -fcoverage-mapping") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage -fprofile-instr-generate -fcoverage-mapping") + endif() + endif() +endif() diff --git a/scripts/diff.js b/scripts/diff.js new file mode 100644 index 0000000..5658d79 --- /dev/null +++ b/scripts/diff.js @@ -0,0 +1,36 @@ +const { execSync } = require('child_process'); +module.exports = () => { + const output = execSync(`git diff origin/master`).toString().split('\n') + let count = 0; + let start = 0; + let current = 0; + let sources = []; + for (let i = 0; i < output.length; i++) { + if (output[i].startsWith('diff --git')) { + // skip 'diff --git a/' prefix + const file = output[i].split(' ')[2].substring(2); + sources.push({'file': file, 'lines': []}); + // go to header line, e.g. '@@ -1,2 +1,3 @@' + do { + ++i; + } + while (!output[i].startsWith('@@')); + output[i].split(' ').forEach((c) => { + if (c.startsWith('+')) { + [start, count] = c.split(','); + start = parseInt(start); + count = parseInt(count); + current = 0; + } + }); + } + else if (current < count && !output[i].startsWith('-')) { + if (output[i].startsWith('+')) { + sources[sources.length - 1].lines.push(start + current); + } + ++current; + } + } + + return sources; +}; diff --git a/scripts/targets.js b/scripts/targets.js new file mode 100644 index 0000000..69fe7d9 --- /dev/null +++ b/scripts/targets.js @@ -0,0 +1,45 @@ +const fs = require('fs'); +const { execSync } = require('child_process'); + +module.exports = (sources) => { + let outputs = []; + let commands = JSON.parse(fs.readFileSync('build/compile_commands.json', 'utf8')); + for (let {'file': src, ..._} of sources) { + if (src.endsWith('.cpp') || src.endsWith('.h') || src.endsWith('.c') + || src.endsWith('.hpp')) { + const idx = commands.findIndex(entry => entry.file.endsWith(src)); + if (idx >= 0) { + let entry = commands[idx]; + if (entry.output !== 'undefined') { + entry.output = entry.command.split(' ').find(token => token.endsWith('.o')); + } + outputs.push(entry.output); + commands.splice(idx, 1); + } + } + } + + targets = new Set(); + while (outputs.length > 0) { + level_targets = new Set(); + for (const output of outputs) { + let lines = execSync(`ninja -C build -t query ${output}`).toString().split('\n'); + + let insert = false; + for (const line of lines) { + if (line.endsWith('outputs:')) { + insert = true; + continue; + } + const value = line.trim(); + if (insert && value.length > 0) { + level_targets.add(value); + } + } + } + + outputs = [...level_targets]; + targets = new Set([...targets, ...level_targets]); + } + return [...targets].join(' '); +} \ No newline at end of file diff --git a/src/my_static_lib.cpp b/src/my_static_lib.cpp index dd9eaa2..3e97251 100644 --- a/src/my_static_lib.cpp +++ b/src/my_static_lib.cpp @@ -4,5 +4,6 @@ int method_in_static_lib() { } int method_in_static_lib2() { - return 43; -} \ No newline at end of file + int p = 65; + return p; +} diff --git a/src/test1.cpp b/src/test1.cpp new file mode 100644 index 0000000..9d07ee5 --- /dev/null +++ b/src/test1.cpp @@ -0,0 +1,9 @@ +int method_in_shared_lib(); + +int main() { + if (42 == method_in_shared_lib()) { + return 0; + } else { + return 1; + } +} \ No newline at end of file diff --git a/src/test2.cpp b/src/test2.cpp new file mode 100644 index 0000000..ab12b55 --- /dev/null +++ b/src/test2.cpp @@ -0,0 +1,9 @@ +int method_in_static_lib(); + +int main() { + if (42 == method_in_static_lib()) { + return 0; + } else { + return 1; + } +} \ No newline at end of file