diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..60ce990 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: ['*'] + commit-message: + prefix: chore(deps) + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..482baf3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI +on: + push: + branches: [main] + pull_request: + merge_group: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: ci-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true +jobs: + native: + name: Native / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, macos-15, windows-2025] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Configure decoder regression tests + run: cmake -S tests -B build -DCMAKE_BUILD_TYPE=Release + - name: Configure dictionary builder + run: cmake -S command -B build-dictionary -DCMAKE_BUILD_TYPE=Release + - name: Build dictionary builder + run: cmake --build build-dictionary --config Release --parallel 4 + - name: Build + run: cmake --build build --config Release --parallel 4 + - name: Run regression tests + run: ctest --test-dir build -C Release --output-on-failure --no-tests=error --timeout 30 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..6e125e7 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,37 @@ +name: CodeQL +on: + push: + branches: [main] + pull_request: + schedule: + - cron: '15 3 * * 1' + workflow_dispatch: +permissions: + contents: read +concurrency: + group: codeql-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true +jobs: + analyze: + name: CodeQL / ${{ matrix.language }} + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: [actions, c-cpp, python] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4 + with: + languages: ${{ matrix.language }} + # C/C++ source analysis complements, rather than replaces, native build CI. + build-mode: none + - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4 + with: + category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..0b492a8 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,44 @@ +name: Repository quality +on: + push: + branches: [main] + pull_request: + merge_group: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: quality-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true +jobs: + workflows: + name: Workflow validation + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + with: + go-version: '1.26' + cache: false + - name: Install actionlint + run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 + - name: Validate workflows and embedded shell scripts + env: + # Ignore style suggestions, but fail on shell correctness warnings and errors. + SHELLCHECK_OPTS: --severity=warning + run: actionlint -color + dependencies: + name: Dependency review + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4 + with: + fail-on-severity: high diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..410e177 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.25) +project(pinyin_decoder_tests LANGUAGES CXX) +include(CTest) +file(GLOB decoder_sources CONFIGURE_DEPENDS "../src/share/*.cpp") +list(FILTER decoder_sources EXCLUDE REGEX "/maintest\\.cpp$") +add_library(pinyin_decoder STATIC ${decoder_sources}) +target_compile_features(pinyin_decoder PUBLIC cxx_std_17) +target_include_directories(pinyin_decoder PUBLIC ../src/include) +add_executable(decoder_smoke decoder_smoke.cpp) +target_link_libraries(decoder_smoke PRIVATE pinyin_decoder) +add_test(NAME decoder_smoke COMMAND decoder_smoke + "${CMAKE_CURRENT_SOURCE_DIR}/../data/dict_pinyin.dat" + "${CMAKE_CURRENT_BINARY_DIR}/test-user.dat") diff --git a/tests/decoder_smoke.cpp b/tests/decoder_smoke.cpp new file mode 100644 index 0000000..11affa9 --- /dev/null +++ b/tests/decoder_smoke.cpp @@ -0,0 +1,26 @@ +#include "pinyinime.h" +#include +#include +#include + +int main(int argc, char** argv) { + if (argc != 3) return 2; + std::remove(argv[2]); + if (!ime_pinyin::im_open_decoder(argv[1], argv[2])) { + std::cerr << "Cannot open decoder dictionary\n"; + return 1; + } + int status = 0; + for (const char* input : {"nihao", "zhongguo", "shurufa"}) { + ime_pinyin::im_reset_search(); + const auto count = ime_pinyin::im_search(input, std::strlen(input)); + ime_pinyin::char16 candidate[256] = {}; + if (!count || !ime_pinyin::im_get_candidate(0, candidate, 256) || !candidate[0]) { + std::cerr << "No candidate for " << input << '\n'; + status = 1; + } + } + ime_pinyin::im_close_decoder(); + std::remove(argv[2]); + return status; +}