diff --git a/.build/build-jars.sh b/.build/build-jars.sh index cf2b91b08201..ce23064a75e2 100755 --- a/.build/build-jars.sh +++ b/.build/build-jars.sh @@ -17,6 +17,25 @@ # temporary between CASSANDRA-18133 and CASSANDRA-18594 +print_help() { + echo "Usage: $0 [-c|--clean] [-s|--summary] [-h|--help]" + echo " -c, --clean Remove locally created artifacts (ant clean) before building" + echo " -s, --summary Print a summary of failures instead of the full ant output" + echo " -h, --help Print help" +} + +# arguments, with defaults +clean=false +summary=false +while [ "$#" -gt 0 ]; do + case "$1" in + -c|--clean) clean=true; shift ;; + -s|--summary) summary=true; shift ;; + -h|--help) print_help; exit 0 ;; + *) echo >&2 "Unknown argument $1"; print_help >&2; exit 1 ;; + esac +done + # variables, with defaults [ "x${CASSANDRA_DIR}" != "x" ] || CASSANDRA_DIR="$(readlink -f $(dirname -- "$0")/..)" @@ -25,6 +44,18 @@ command -v ant >/dev/null 2>&1 || { echo >&2 "ant needs to be installed"; exit 1 [ -d "${CASSANDRA_DIR}" ] || { echo >&2 "Directory ${CASSANDRA_DIR} must exist"; exit 1; } [ -f "${CASSANDRA_DIR}/build.xml" ] || { echo >&2 "${CASSANDRA_DIR}/build.xml must exist"; exit 1; } +# run ant, summarizing failures when --summary (summary exit code mirrors the build) +run_ant() { + if ${summary}; then + ant -f "${CASSANDRA_DIR}/build.xml" "$@" 2>&1 | "${CASSANDRA_DIR}/.build/sh/ant-log-summary.py" - + else + ant -f "${CASSANDRA_DIR}/build.xml" "$@" + fi +} + # execute -ant -f "${CASSANDRA_DIR}/build.xml" jar +if ${clean}; then + run_ant clean +fi +run_ant jar exit $? diff --git a/.build/check-code.sh b/.build/check-code.sh index 4a540a653d4d..cf09bd85fa11 100755 --- a/.build/check-code.sh +++ b/.build/check-code.sh @@ -15,6 +15,22 @@ # See the License for the specific language governing permissions and # limitations under the License. +print_help() { + echo "Usage: $0 [-s|--summary] [-h|--help]" + echo " -s, --summary Print a summary of failures instead of the full ant output" + echo " -h, --help Print help" +} + +# arguments, with defaults +summary=false +while [ "$#" -gt 0 ]; do + case "$1" in + -s|--summary) summary=true; shift ;; + -h|--help) print_help; exit 0 ;; + *) echo >&2 "Unknown argument $1"; print_help >&2; exit 1 ;; + esac +done + # variables, with defaults [ "x${CASSANDRA_DIR}" != "x" ] || { CASSANDRA_DIR="$(dirname -- "$0")/.."; } @@ -24,5 +40,12 @@ command -v ant >/dev/null 2>&1 || { echo >&2 "ant needs to be installed"; exit 1 [ -f "${CASSANDRA_DIR}/build.xml" ] || { echo >&2 "${CASSANDRA_DIR}/build.xml must exist"; exit 1; } # execute. memory needs to fit within the specified container size, see .jenkins/Jenkinsfile -ANT_OPTS="-Xmx2g -XX:+PrintClassHistogram -XX:OnOutOfMemoryError='kill -QUIT %p'" ant -f "${CASSANDRA_DIR}/build.xml" check # dependency-check # FIXME dependency-check now requires NVD key downloaded first +# dependency-check # FIXME dependency-check now requires NVD key downloaded first +export ANT_OPTS="-Xmx2g -XX:+PrintClassHistogram -XX:OnOutOfMemoryError='kill -QUIT %p'" +if ${summary}; then + # summarize failures; the summary's exit code mirrors the build + ant -f "${CASSANDRA_DIR}/build.xml" check 2>&1 | "${CASSANDRA_DIR}/.build/sh/ant-log-summary.py" - +else + ant -f "${CASSANDRA_DIR}/build.xml" check +fi exit $? diff --git a/.build/run-tests.sh b/.build/run-tests.sh index 7d6223137701..8ba8da428c58 100755 --- a/.build/run-tests.sh +++ b/.build/run-tests.sh @@ -31,7 +31,13 @@ set -o pipefail # target types TARGET_TYPES="build_dtest_jars stress-test fqltool-test sstableloader-test microbench microbench-test test-burn long-test cqlsh-test simulator-dtest test test-cdc test-compression test-oa test-system-keyspace-directory test-latest jvm-dtest jvm-dtest-upgrade jvm-dtest-novnode jvm-dtest-upgrade-novnode" -# pre-conditions +error() { + echo >&2 $2; + set -x + exit $1 +} + +# pre-conditions (error() must be defined above) command -v ant >/dev/null 2>&1 || { error 1 "ant needs to be installed"; } command -v git >/dev/null 2>&1 || { error 1 "git needs to be installed"; } command -v uuidgen >/dev/null 2>&1 || test -f /proc/sys/kernel/random/uuid || { error 1 "uuidgen needs to be installed"; } @@ -39,13 +45,6 @@ command -v uuidgen >/dev/null 2>&1 || test -f /proc/sys/kernel/random/uuid || { [ -f "${CASSANDRA_DIR}/build.xml" ] || { error 1 "${CASSANDRA_DIR}/build.xml must exist"; } [ -d "${DIST_DIR}" ] || { mkdir -p "${DIST_DIR}" ; } - -error() { - echo >&2 $2; - set -x - exit $1 -} - print_help() { echo "Usage: $0 [-a|-t|-c|-e|-i|-b|-s|-h]" echo " -a Test target type: ${TARGET_TYPES}" @@ -54,13 +53,12 @@ print_help() { echo " -b Specify the base git branch for comparison when determining changed tests to" echo " repeat. Defaults to ${BASE_BRANCH}. Note that this option is not used when" echo " the '-a' option is specified." - echo " -s Skip automatic detection of changed tests. Useful when you need to repeat a few ones," - echo " or when there are too many changed tests the CI env to handle." echo " -e Environment variables to be used in the repeated runs:" echo " -e REPEATED_TESTS_STOP_ON_FAILURE=false" echo " -e REPEATED_TESTS_COUNT=500" echo " If you want to specify multiple environment variables simply add multiple -e options." echo " -i Ignore unknown environment variables" + echo " -s Print a summary of failed tests instead of the full ant output" echo " -h Print help" } @@ -84,7 +82,8 @@ fi env_vars="" has_env_vars=false check_env_vars=true -detect_changed_tests=true +summary=false + while getopts "a:t:c:e:ib:shj:" opt; do case $opt in a ) test_target="$OPTARG" @@ -105,7 +104,7 @@ while getopts "a:t:c:e:ib:shj:" opt; do ;; i ) check_env_vars=false ;; - s ) detect_changed_tests=false + s ) summary=true ;; h ) print_help exit 0 @@ -331,7 +330,15 @@ _run_microbench() { local -r arch="$(uname -m)" local -r output_dir="${DIST_DIR}/test/output/${_target}/jdk${java_version}/${arch}/${_split_chunk//\//_}" + # microbench produces JMH json, not JUnit xml, so generate-test-report has + # nothing to summarise. Emit a "failed ..." line (matched by + # test-log-summary.py) so a benchmark failure is not reported as a pass in + # -s mode, where errexit is disabled and the ant failure would be swallowed. + set +o errexit ant $_target ${ANT_TEST_OPTS} -Dbuild.test.output.dir=${output_dir} -Dbenchmark.name="${benchmark_pattern}" -Dmaven.test.failure.ignore=true + local -r _ant_status=$? + set -o errexit + [[ ${_ant_status} -ne 0 ]] && echo "failed ${_target} ${_split_chunk}" # Post-process jmh-result.json to add jdk and arch parameters local jmh_result="${output_dir}/jmh-result.json" @@ -357,7 +364,7 @@ _main() { local -r split_chunk="${chunk:-1/1}" # Chunks formatted as "K/N" for the Kth chunk of N chunks # check split_chunk is compatible with target (if not a regexp) - if [[ "${_split_chunk}" =~ ^\d+/\d+$ ]] && [[ "1/1" != "${split_chunk}" ]] ; then + if [[ "${split_chunk}" =~ ^[0-9]+/[0-9]+$ ]] && [[ "1/1" != "${split_chunk}" ]] ; then case ${target} in "stress-test" | "fqltool-test" | "cqlsh-test" | "sstableloader-test") echo "Target ${target} does not support splits." @@ -401,8 +408,12 @@ _main() { [ -d ${TMP_DIR} ] || mkdir -p "${TMP_DIR}" export ANT_TEST_OPTS="-Dno-build-test=true -Dtmp.dir=${TMP_DIR} -Dbuild.test.output.dir=${DIST_DIR}/test/output/${target}" - # fresh virtualenv and test logs results everytime - [[ "/" == "${DIST_DIR}" ]] || rm -rf "${DIST_DIR}/test/{html,output,logs,reports}" + # fresh virtualenv and test logs results everytime. + # NB: braces are intentionally unquoted so brace-expansion produces the four + # paths; quoting them deletes a single literal '{html,output,logs,reports}' + # path (a no-op), leaving stale TEST-*.xml that generate-test-report would + # then merge into the summary. + [[ "/" == "${DIST_DIR}" ]] || rm -rf ${DIST_DIR}/test/{html,output,logs,reports} # cheap trick to ensure dependency libraries are in place. allows us to stash only project specific build artifacts. # also recreate some of the non-build files we need @@ -492,10 +503,30 @@ _main() { ;; esac - # merge all unit xml files into one, and print summary test numbers - ant -quiet -silent generate-test-report + # merge all unit xml files into one, and print summary test numbers. + # Pin the output/report dirs to DIST_DIR; otherwise the report scans ant's + # default ${basedir}/build/test/output and, when DIST_DIR is overridden, + # finds no results and reports a false pass. + ant -quiet -silent generate-test-report \ + -Dbuild.test.output.dir="${DIST_DIR}/test/output" \ + -Dbuild.test.report.dir="${DIST_DIR}/test/reports" popd >/dev/null } -_main "$@" +if ${summary}; then + # Summarize the run: run-tests.sh keeps going past failing tests (ant still + # prints BUILD SUCCESSFUL), so the summary's exit code — derived from the + # test results — is what determines pass/fail here. stderr (where error() + # writes) is left untouched so setup failures stay visible. + # Disable errexit/pipefail so a non-zero _main doesn't pre-empt the summary. + set +o errexit +o pipefail + _main "$@" | "${CASSANDRA_DIR}/.build/sh/test-log-summary.py" - + main_status=${PIPESTATUS[0]} + summary_status=${PIPESTATUS[1]} + # a setup failure inside _main (error() exits non-zero) takes precedence + [[ ${main_status} -ne 0 ]] && exit ${main_status} + exit ${summary_status} +else + _main "$@" +fi diff --git a/.build/sh/ai-build b/.build/sh/ai-build deleted file mode 100755 index fa69b1b277d9..000000000000 --- a/.build/sh/ai-build +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -#set -o xtrace -set -o errexit -set -o pipefail -set -o nounset - -bin="$(cd "$(dirname "$0")" > /dev/null; pwd)" -home="$(cd "$bin/../.." > /dev/null; pwd)" # this script lives in .build/sh - -usage() { - if [[ $# -gt 0 ]]; then - echo "$*" 1>&2 - fi - cat <&1 | "$bin"/ant-log-summary.py - -else - ant "${opts[@]}" clean jar checkstyle checkstyle-test "$@" 2>&1 | "$bin"/ant-log-summary.py - -fi - diff --git a/.build/sh/ai-ci-test b/.build/sh/ai-ci-test deleted file mode 100755 index 9e791cd0f1e6..000000000000 --- a/.build/sh/ai-ci-test +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -#set -o xtrace -set -o errexit -set -o pipefail -set -o nounset - -bin="$(cd "$(dirname "$0")" > /dev/null; pwd)" -home="$(cd "$bin/../.." > /dev/null; pwd)" # this script lives in .build/sh - -# Switch to the project root so all commands work properly -cd "$home" - -"$bin"/ci-test "$@" 2>&1 | "$bin"/ant-log-summary.py - diff --git a/.build/sh/ci-test b/.build/sh/ci-test deleted file mode 100755 index 091dbb1d5666..000000000000 --- a/.build/sh/ci-test +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env bash -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -#set -o xtrace -set -o errexit -set -o pipefail -set -o nounset - -bin="$(cd "$(dirname "$0")" > /dev/null; pwd)" -home="$(cd "$bin/../.." > /dev/null; pwd)" # this script lives in .build/sh - -# Switch to the project root so all commands work properly -cd "$home" - -error() { - echo "ERROR: $*" 1>&2 - exit 1 -} - -usage() { - if [[ $# -gt 0 ]]; then - echo "$*" 1>&2 - fi - cat <&1 | awk -F '"' '/version/ {print $2}' | awk -F. '{print $1}') - -if [ "$JAVA_VERSION" -ge 11 ]; then - export CASSANDRA_USE_JDK11=true -fi - -if [ -n "${num_tokens:-}" ]; then - export CASSANDRA_DTEST_NUM_TOKENS="$num_tokens" -fi - -opts=( - -Dant.gen-doc.skip=true - -Dno-checkstyle=true - -Dant.gen-doc.skip=true - -Drat.skip=true -) -if [[ -n "${jvm_dtest_version:-}" ]]; then - opts+=( "-Ddtest-api.version=$jvm_dtest_version" ) -fi - -if [[ "$fresh_build" == true ]]; then - if [[ ! "$path" =~ distributed/upgrade ]]; then - ant "${opts[@]}" realclean - ant "${opts[@]}" - ant "${opts[@]}" generate-idea-files - fi -fi - -# cleanup logs so w/e is around are for this run -rm -rf build/test/logs || true - -test_timeout=$(grep "name=\"test.$prefix.timeout\"" build.xml | awk -F'"' '{print $4}' || true) -if [ -z "$test_timeout" ]; then - test_timeout=$(grep 'name="test.timeout"' build.xml | awk -F'"' '{print $4}') -fi -if [[ "$fresh_build" == false ]]; then - opts+=(-Dno-build-test=true) -fi - -if [[ "$prefix" == "simulator" ]]; then - ant "${opts[@]}" test-simulator-dtest -Dtest.name="$(echo "$path" | awk -F'.' '{print $1}' | awk -F'/' '{print $NF}')" -else - ant "${opts[@]}" "$task" -Dtest.timeout="$test_timeout" -Dtest.classlistfile=<(echo "$path") -Dtest.classlistprefix="$prefix" -fi diff --git a/.build/sh/ci-test-loop b/.build/sh/ci-test-loop deleted file mode 100755 index 8d44fe1427c9..000000000000 --- a/.build/sh/ci-test-loop +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -#set -o xtrace -set -o errexit -set -o pipefail -set -o nounset - -bin="$(cd "$(dirname "$0")" > /dev/null; pwd)" -home="$(cd "$bin/../.." > /dev/null; pwd)" # this script lives in .build/sh - -# Switch to the project root so all commands work properly -cd "$home" - -usage() { - if [[ $# -gt 0 ]]; then - echo "$*" 1>&2 - fi - cat <>> Running attempt $counter" - while "$bin"/ci-test "$class_name" --reuse; do - counter=$(( counter + 1 )) - echo ">>> Running attempt $counter" - done; - echo "Exited after $counter attempts" -} - -_main "$@" diff --git a/.build/sh/test-log-summary.py b/.build/sh/test-log-summary.py new file mode 100755 index 000000000000..a7f67101079f --- /dev/null +++ b/.build/sh/test-log-summary.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Summarize the output of .build/run-tests.sh. +# +# Unlike ant-log-summary.py (which keys off ant's BUILD FAILED/SUCCESSFUL), +# run-tests.sh deliberately continues past failing tests, as ant still prints +# BUILD SUCCESSFUL with test failures. +# +# Failures are identified from the test results instead: +# - the "[Test Summary] Run: N, Failed: N, Errors: N, Skipped: N" line +# emitted by ant's generate-test-report target, +# - run-tests.sh's own "failed ..." lines, +# - "failure rate: X/Y" lines printed for repeated runs, +# - per-test "Testcase: :\tFAILED / Caused an ERROR" markers from the +# brief JUnit formatter, +# - a compile/setup "BUILD FAILED" (with the [javac] errors). +# +# The exit code mirrors the outcome: non-zero if any failure signal is seen. + +import argparse +import re +import sys + +TEST_SUMMARY_RE = re.compile( + r"\[Test Summary\]\s*Run:\s*(\d+),\s*Failed:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)" +) +FAILURE_RATE_RE = re.compile(r"failure rate:\s*(\d+)/(\d+)") +# run-tests.sh: echo "failed ${_target_prefix} ${_testlist_target} ..." +RUN_TESTS_FAILED_RE = re.compile(r"^failed\s+\S+") +# brief JUnit formatter: "Testcase: :\tFAILED" / ":\tCaused an ERROR", +# optionally prefixed by an ant task tag such as "[junit] ". +TESTCASE_FAIL_RE = re.compile(r"^(?:\[[^\]]+\]\s*)?Testcase:\s.*\b(FAILED|Caused an ERROR)\b") + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Summarize Apache Cassandra .build/run-tests.sh output" + ) + parser.add_argument( + "log_file", + nargs="?", + default="-", + help='Path to the run-tests.sh log (use "-" or omit to read from stdin)', + ) + return parser.parse_args() + + +ANT_TAG_RE = re.compile(r"^\[[^\]]+\]\s*") + + +def _strip_tag(line): + """Drop a leading ant task tag such as '[junit] ' or '[echo] '.""" + return ANT_TAG_RE.sub("", line.strip()) + + +def summarize(content): + """Return (failed, lines) where failed is a bool and lines is the summary.""" + lines = content.split("\n") + + summaries = [] # [Test Summary] lines + failed_targets = [] # run-tests.sh "failed ..." lines + failure_rates = [] # "failure rate: X/Y" lines + failed_testcases = [] # per-test FAILED / ERROR markers + javac_errors = [] # compile errors, when a BUILD FAILED is present + + build_failed = "BUILD FAILED" in content + + for line in lines: + stripped = line.strip() + m = TEST_SUMMARY_RE.search(line) + if m: + run, failures, errors, skipped = (int(g) for g in m.groups()) + summaries.append((_strip_tag(line), failures, errors)) + continue + if RUN_TESTS_FAILED_RE.match(stripped): + failed_targets.append(stripped) + continue + m = FAILURE_RATE_RE.search(line) + if m: + failure_rates.append((stripped, int(m.group(1)))) + continue + if TESTCASE_FAIL_RE.match(stripped): + failed_testcases.append(_strip_tag(line)) + continue + if build_failed and "[javac]" in line and ("error:" in line or "errors" in line): + clean = line.replace("[javac]", "").strip() + if clean: + javac_errors.append(clean) + + # decide pass/fail + failed = build_failed + for _, failures, errors in summaries: + if failures or errors: + failed = True + if failed_targets: + failed = True + for _, n in failure_rates: + if n: + failed = True + + # build the summary output + out = [] + if build_failed: + out.append("BUILD FAILED") + if javac_errors: + out.append("") + out.append("Compilation Errors:") + out.append("-" * 20) + out.extend(javac_errors) + + if failed_testcases: + out.append("") + out.append("Failed tests:") + out.append("-" * 13) + # de-duplicate while preserving order + seen = set() + for tc in failed_testcases: + if tc not in seen: + seen.add(tc) + out.append(tc) + + if failed_targets: + out.append("") + out.extend(failed_targets) + + for line, _ in failure_rates: + out.append(line) + + if summaries: + out.append("") + for line, _, _ in summaries: + out.append(line) + elif not build_failed and not failed: + # nothing ran a test report and nothing failed + out.append("No test summary found (nothing ran, or non-test target).") + + if not failed and summaries: + out.append("") + out.append("TESTS PASSED") + + return failed, out + + +def main(): + args = parse_args() + + try: + if args.log_file == "-": + content = sys.stdin.read() + else: + with open(args.log_file, "r") as f: + content = f.read() + except FileNotFoundError: + print(f"Error: Log file '{args.log_file}' not found") + sys.exit(2) + except Exception as e: + print(f"Error reading log file: {e}") + sys.exit(2) + + failed, out = summarize(content) + print("\n".join(out)) + sys.exit(1 if failed else 0) + + +if __name__ == "__main__": + main() diff --git a/AGENTS.md b/AGENTS.md index 1a6b6a947bbc..5b114072497d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,31 +14,38 @@ Apache Cassandra is a NoSQL distributed database. This is the official Git repos ## Build +Prefer the `.build/*.sh` helper scripts over calling `ant` directly. See +[.build/README.md](./.build/README.md) for the full set. + ```bash -.build/sh/ai-build # clean, build JAR, and run checkstyle (output is summarized) +.build/build-jars.sh -s # build the Cassandra JAR (runs `ant jar`) +.build/build-jars.sh -s --clean # clean first, then build. `-s` summarizes output, remove for verbose ``` -Do NOT call `ant` directly — always use the `ai-*` wrapper scripts which handle log summarization and correct working directory. - ## Testing - Do NOT run the entire test suite. Run only the specific test(s) relevant to your change. +- The project must be built first (e.g. `.build/build-jars.sh`). ```bash - # Run a single unit test class - .build/sh/ai-ci-test org.apache.cassandra.service.StorageServiceServerTest + # Run a single test class: -a is the test type, -t is a class-name regexp + .build/run-tests.sh -s -a test -t StorageServiceServerTest ``` -- `ai-ci-test` does NOT support method-level filtering — it runs the entire test class. +- `-a` is the test type (`test`, `jvm-dtest`, `long-test`, …; run `.build/run-tests.sh -h` for the full list). +- `-t` matches the test class only, not individual methods. +- `-s`/`--summary` provides a concise list of failed tests, remove for the full ant output - When fixing a bug, first create a regression test that reproduces the failure, then implement the fix and verify. - Provide test(s) coverage for all new or modified code. ## Linting and Code Checks -`.build/sh/ai-build` includes checkstyle validation. There is no need to run checkstyle separately. +```bash +.build/check-code.sh -s # runs `ant check` (build + checkstyle + checkstyle-test). `-s` summarizes output, remove for verbose +``` ## Code Style -Cassandra enforces style via Checkstyle (run via `.build/sh/ai-build`). The official style guide is at https://cassandra.apache.org/_/development/code_style.html. Always defer to it when in doubt. +Cassandra enforces style via Checkstyle (run via `.build/check-code.sh`). The official style guide is at https://cassandra.apache.org/_/development/code_style.html. Always defer to it when in doubt. General style conventions: - 4-space indentation, no tabs. @@ -51,10 +58,12 @@ General style conventions: - Commit messages format. For example: ``` - + + patch by ; reviewed by for CASSANDRA-##### + Assisted-by: AGENT_NAME:MODEL_VERSION ```