diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ee1f96f..bc93821 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,12 +15,17 @@ name: Tests on: pull_request: + types: [opened, reopened, synchronize] push: branches: - main + merge_group: + types: [checks_requested] + workflow_call: permissions: contents: read + actions: write jobs: test-sanitizers: @@ -147,3 +152,64 @@ jobs: - name: Verify ${{ matrix.label }} sanitizer catches bugs (negative) working-directory: tests run: bazel test --config=${{ matrix.config }} //:${{ matrix.runtime_target }} --verbose_failures + + test-warnings: + name: Warnings feature (${{ matrix.label }}) + runs-on: ubuntu-24.04 + + strategy: + fail-fast: false + matrix: + include: + - label: minimal_warnings + config: feature_only_gcc_minimal_warnings + violation_target: minimal_warnings_violation + expected_diagnostic: "-W(error=)?cast-qual" + - label: strict_warnings + config: feature_only_gcc_strict_warnings + violation_target: strict_warnings_violation + expected_diagnostic: "-W(error=)?vla" + - label: all_wall_warnings + config: feature_only_gcc_all_wall_warnings + violation_target: all_wall_warnings_violation + expected_diagnostic: "-W(error=)?unused-variable" + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.18.0 + with: + bazelisk-cache: true + cache-save: ${{ github.event_name == 'push' }} + + - name: Verify ${{ matrix.label }} + warnings_as_errors has no false positives on clean code + working-directory: tests + run: bazel test --config=${{ matrix.config }} //:warnings_positive_test --verbose_failures + + # This is a compile-time (build) failure, not a runnable test, so we invert + # the exit code here instead of relying on `bazel test` (which doesn't + # apply to cc_library targets). We also grep the log for the specific + # diagnostic each violation file documents, so an unrelated build failure + # (bad flag, missing toolchain, typo, ...) isn't mistaken for a pass. + - name: Verify ${{ matrix.label }} catches its documented violation (negative) + working-directory: tests + run: | + set +e + bazel build --config=${{ matrix.config }} //:${{ matrix.violation_target }} --verbose_failures > build.log 2>&1 + STATUS=$? + set -e + cat build.log + + if [ "$STATUS" -eq 0 ]; then + echo "::error::Expected //:${{ matrix.violation_target }} to fail to build under --config=${{ matrix.config }}, but it succeeded" + exit 1 + fi + + if ! grep -qE -- '${{ matrix.expected_diagnostic }}' build.log; then + echo "::error:://:${{ matrix.violation_target }} failed to build, but not because of the expected '${{ matrix.expected_diagnostic }}' diagnostic — see log above for the actual cause" + exit 1 + fi + + diff --git a/docs/warnings.md b/docs/warnings.md index 8669e25..34b33a5 100644 --- a/docs/warnings.md +++ b/docs/warnings.md @@ -327,3 +327,73 @@ enabled to a hard compile error. |---|---|---| | `-Werror` | Linux & QNX | Turn every currently-enabled warning into a compile error, so a build cannot succeed while warnings remain. | | `-Wno-error=deprecated-declarations` | Linux only | Exempt deprecated-declaration warnings from the `-Werror` escalation above — deprecations are advisory and shouldn't block a build. | + +--- + +## Testing + +The [`tests/`](../tests) module exercises the warnings features on GCC. It +registers a dedicated toolchain (`score_gcc_toolchain_15`, GCC 15.3.0) in +[`tests/MODULE.bazel`](../tests/MODULE.bazel) with `minimal_warnings`, +`strict_warnings`, `all_wall_warnings`, and `warnings_as_errors` added to +`extra_known_features` — this is the toolchain that must be used to test +these features; it is **not** the same toolchain used for the sanitizer +tests (`score_gcc_x86_64_toolchain_fi`). + +[`tests/.bazelrc`](../tests/.bazelrc) defines one `--config` per severity +level, each pinning that toolchain and bundling `warnings_as_errors` so a +false positive turns into a build failure instead of a silent warning: + +| Config | Feature enabled | +|---|---| +| `feature_only_gcc_minimal_warnings` | `minimal_warnings` + `warnings_as_errors` | +| `feature_only_gcc_strict_warnings` | `strict_warnings` + `warnings_as_errors` | +| `feature_only_gcc_all_wall_warnings` | `all_wall_warnings` + `warnings_as_errors` | + +### Positive test — no false positives + +[`tests/warnings/positive_test.cpp`](../tests/warnings/positive_test.cpp) is +idiomatic code that must compile clean at every severity level. Run it as a +normal test, e.g.: + +```bash +cd tests +bazel test --config=feature_only_gcc_minimal_warnings //:warnings_positive_test +``` + +### Negative targets — each severity actually catches its violation + +`minimal_warnings_violation`, `strict_warnings_violation`, and +`all_wall_warnings_violation` are `cc_library` targets tagged `manual`, +each containing one documented, intentional violation (see the header +comment in the corresponding file: +[`tests/warnings/minimal_violation.cpp`](../tests/warnings/minimal_violation.cpp), +[`tests/warnings/strict_violation.cpp`](../tests/warnings/strict_violation.cpp), +[`tests/warnings/all_wall_violation.cpp`](../tests/warnings/all_wall_violation.cpp)). +They must **fail to build** under their matching config: + +```bash +cd tests +bazel build --config=feature_only_gcc_minimal_warnings //:minimal_warnings_violation +``` + +These are `cc_library` targets, not tests — `bazel test` doesn't apply to +them (there's no runnable action), so CI invokes `bazel build` directly and +inverts the exit code (see `test-warnings` in +[`.github/workflows/tests.yml`](../.github/workflows/tests.yml)): + +```bash +if bazel build --config=feature_only_gcc_minimal_warnings //:minimal_warnings_violation; then + echo "expected this build to fail, but it succeeded" >&2 + exit 1 +fi +``` + +To confirm a violation file builds clean *without* the warnings feature +enabled (i.e. the failure above really comes from the feature, not from +some other compiler default), build it against the plain toolchain: + +```bash +cd tests +bazel build --extra_toolchains=@score_gcc_toolchain_15//:x86_64-linux-gcc_15.3.0 //:minimal_warnings_violation +``` diff --git a/tests/.bazelrc b/tests/.bazelrc index 7b9bc5b..aec84aa 100644 --- a/tests/.bazelrc +++ b/tests/.bazelrc @@ -76,3 +76,27 @@ test:feature_only_gcc_lsan --test_env=LSAN_OPTIONS=exitcode=55 build:feature_only_gcc_tsan --extra_toolchains=@score_gcc_x86_64_toolchain_fi//:x86_64-linux build:feature_only_gcc_tsan --features=score_tsan test:feature_only_gcc_tsan --test_env=TSAN_OPTIONS=exitcode=55:halt_on_error=1 + +# Warnings features (warnings/gcc) are GCC-only, so these always pin the GCC +# toolchain. Each config also enables warnings_as_errors so a false positive at +# that severity level turns into a build failure instead of a silent warning. +# Shared configuration for simple test execution +build:shared --incompatible_strict_action_env +build:shared --sandbox_writable_path=/var/tmp + +build:x86_64-linux-gcc15 --config=shared +build:x86_64-linux-gcc15 --host_platform=@score_bazel_platforms//:x86_64-linux-gcc_15.3.0-posix +build:x86_64-linux-gcc15 --extra_toolchains=@score_gcc_toolchain_15//:x86_64-linux-gcc_15.3.0 +build:x86_64-linux-gcc15 --host_features=use_pthread + +build:feature_only_gcc_minimal_warnings --config=x86_64-linux-gcc15 +build:feature_only_gcc_minimal_warnings --features=minimal_warnings +build:feature_only_gcc_minimal_warnings --features=warnings_as_errors + +build:feature_only_gcc_strict_warnings --config=x86_64-linux-gcc15 +build:feature_only_gcc_strict_warnings --features=strict_warnings +build:feature_only_gcc_strict_warnings --features=warnings_as_errors + +build:feature_only_gcc_all_wall_warnings --config=x86_64-linux-gcc15 +build:feature_only_gcc_all_wall_warnings --features=all_wall_warnings +build:feature_only_gcc_all_wall_warnings --features=warnings_as_errors diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 56c2344..a3e5602 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -234,4 +234,35 @@ sh_test( target_compatible_with = ["@score_cpp_policies//sanitizers/constraints:only_ubsan"], ) +# ============================================================================== +# Warnings feature tests (warnings/gcc) - GCC-only, see .bazelrc feature_only_gcc_*_warnings +# ============================================================================== + +# Must build clean at every severity level, even combined with warnings_as_errors. +# Run with e.g.: bazel test --config=feature_only_gcc_all_wall_warnings :warnings_positive_test +cc_test( + name = "warnings_positive_test", + srcs = ["warnings/positive_test.cpp"], +) + +# Documented, intentional violations - one per severity level. Not built by +# default (tags = ["manual"]); see each file's header comment to verify manually. +cc_library( + name = "minimal_warnings_violation", + srcs = ["warnings/minimal_violation.cpp"], + tags = ["manual"], +) + +cc_library( + name = "strict_warnings_violation", + srcs = ["warnings/strict_violation.cpp"], + tags = ["manual"], +) + +cc_library( + name = "all_wall_warnings_violation", + srcs = ["warnings/all_wall_violation.cpp"], + tags = ["manual"], +) + # ======================================================================= diff --git a/tests/MODULE.bazel b/tests/MODULE.bazel index a08b5aa..8747aca 100644 --- a/tests/MODULE.bazel +++ b/tests/MODULE.bazel @@ -16,21 +16,16 @@ module(name = "score_cpp_policies_tests") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "rules_cc", version = "0.2.17") bazel_dep(name = "toolchains_llvm", version = "1.7.0") +bazel_dep(name = "score_bazel_platforms", version = "1.0.0") bazel_dep(name = "score_cpp_policies") local_path_override( module_name = "score_cpp_policies", path = "..", ) -bazel_dep(name = "score_bazel_cpp_toolchains") -git_override( - module_name = "score_bazel_cpp_toolchains", - commit = "942559c7dcdfa72d53343fcf048cade00d4accb8", - remote = "https://github.com/eclipse-score/bazel_cpp_toolchains", -) - # --- GCC toolchain --- +bazel_dep(name = "score_bazel_cpp_toolchains", version = "1.0.2") gcc = use_extension("@score_bazel_cpp_toolchains//extensions:gcc.bzl", "gcc") gcc.toolchain( name = "score_gcc_x86_64_toolchain_fi", @@ -48,7 +43,20 @@ gcc.toolchain( use_default_package = True, version = "12.2.0", ) -use_repo(gcc, "score_gcc_x86_64_toolchain_fi") +gcc.toolchain( + name = "score_gcc_toolchain_15", + extra_known_features = [ + "@score_cpp_policies//warnings/gcc/features:minimal_warnings", + "@score_cpp_policies//warnings/gcc/features:strict_warnings", + "@score_cpp_policies//warnings/gcc/features:all_wall_warnings", + "@score_cpp_policies//warnings/gcc/features:warnings_as_errors", + ], + target_cpu = "x86_64", + target_os = "linux", + use_default_package = True, + version = "15.3.0", +) +use_repo(gcc, "score_gcc_x86_64_toolchain_fi", "score_gcc_toolchain_15") # --- Clang toolchain --- diff --git a/tests/warnings/all_wall_violation.cpp b/tests/warnings/all_wall_violation.cpp new file mode 100644 index 0000000..4a6cb0a --- /dev/null +++ b/tests/warnings/all_wall_violation.cpp @@ -0,0 +1,27 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +// Intentional -Wunused-variable violation (part of `all_wall_warnings`). +// This file exists to document what all_wall_warnings catches. +// It must NOT be included in the regular test build (tags = ["manual"]). +// +// Verify with: +// bazel build --config=feature_only_gcc_all_wall_warnings :all_wall_warnings_violation +// -> fails (warnings_as_errors is bundled into the config) +// bazel build --extra_toolchains=@score_gcc_toolchain_15//:x86_64-linux-gcc_15.3.0 :all_wall_warnings_violation +// -> succeeds silently (no warnings feature enabled) + +int compute() { + int unused_value = 42; // -Wunused-variable + return 0; +} diff --git a/tests/warnings/minimal_violation.cpp b/tests/warnings/minimal_violation.cpp new file mode 100644 index 0000000..6a56724 --- /dev/null +++ b/tests/warnings/minimal_violation.cpp @@ -0,0 +1,27 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +// Intentional -Wcast-qual violation (part of `minimal_warnings`). +// This file exists to document what minimal_warnings catches. +// It must NOT be included in the regular test build (tags = ["manual"]). +// +// Verify with: +// bazel build --config=feature_only_gcc_minimal_warnings :minimal_warnings_violation +// -> fails (warnings_as_errors is bundled into the config) +// bazel build --extra_toolchains=@score_gcc_toolchain_15//:x86_64-linux-gcc_15.3.0 :minimal_warnings_violation +// -> succeeds silently (no warnings feature enabled) + +int strip_const(const int* p) { + int* mutable_p = (int*)p; // -Wcast-qual: C-style cast discards `const` + return *mutable_p; +} diff --git a/tests/warnings/positive_test.cpp b/tests/warnings/positive_test.cpp new file mode 100644 index 0000000..a26b69b --- /dev/null +++ b/tests/warnings/positive_test.cpp @@ -0,0 +1,31 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +// Must compile clean under minimal/strict/all_wall_warnings, even combined with +// warnings_as_errors (-Werror) — proves the policy has no false positives on +// idiomatic code. See feature_only_gcc_{minimal,strict,all_wall}_warnings in .bazelrc. + +#include + +namespace { + +std::uint32_t add(std::uint32_t a, std::uint32_t b) { + return a + b; +} + +} // namespace + +int main() { + const std::uint32_t result = add(2, 3); + return result == 5 ? 0 : 1; +} diff --git a/tests/warnings/strict_violation.cpp b/tests/warnings/strict_violation.cpp new file mode 100644 index 0000000..2873327 --- /dev/null +++ b/tests/warnings/strict_violation.cpp @@ -0,0 +1,32 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +// Intentional -Wvla violation (part of `strict_warnings`). +// This file exists to document what strict_warnings catches. +// It must NOT be included in the regular test build (tags = ["manual"]). +// +// Verify with: +// bazel build --config=feature_only_gcc_strict_warnings :strict_warnings_violation +// -> fails (warnings_as_errors is bundled into the config) +// bazel build --extra_toolchains=@score_gcc_toolchain_15//:x86_64-linux-gcc_15.3.0 :strict_warnings_violation +// -> succeeds silently (no warnings feature enabled) + +int sum_first_n(int n) { + int values[n]; // -Wvla: variable-length array + int total = 0; + for (int i = 0; i < n; ++i) { + values[i] = i; + total += values[i]; + } + return total; +}