From 1403c4b93a7967818e8d9e12e00fa4467ad1aede Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Wed, 25 Mar 2026 16:13:51 +0800 Subject: [PATCH 1/8] CI: add gpMgmt Behave test --- .github/workflows/build-cloudberry.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/build-cloudberry.yml b/.github/workflows/build-cloudberry.yml index adb57fb85ec..d5eb728b90f 100644 --- a/.github/workflows/build-cloudberry.yml +++ b/.github/workflows/build-cloudberry.yml @@ -338,6 +338,10 @@ jobs: }, {"test":"ic-cbdb-parallel", "make_configs":["src/test/regress:installcheck-cbdb-parallel"] + }, + {"test":"ic-behave", + "make_configs":["gpMgmt/:behave"], + "enable_core_check":false } ] }' @@ -1448,6 +1452,22 @@ jobs: config="${configs[$i]}" IFS=':' read -r dir target <<< "$config" + if [ "${{ matrix.test }}" == "ic-behave" ]; then + echo "=== Executing Behave test ===" + config_log="build-logs/details/make-${{ matrix.test }}-config$i.log" + + echo "Installing Python dependencies for Behave..." + su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" + + if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && make -f Makefile.behave behave" \ + 2>&1 | tee -a "$config_log"; then + echo "::warning::Test execution failed for Behave" + overall_status=1 + fi + echo "=== End configuration $((i+1)) execution ===" + continue + fi + echo "=== Executing configuration $((i+1))/${#configs[@]} ===" echo "Make command: make -C $dir $target" echo "Environment:" From 8632d00287aa3937644900746db037fcde1cab23 Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Wed, 25 Mar 2026 16:57:45 +0800 Subject: [PATCH 2/8] Fix libffi cmd not found --- .github/workflows/build-cloudberry.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-cloudberry.yml b/.github/workflows/build-cloudberry.yml index d5eb728b90f..94927c90732 100644 --- a/.github/workflows/build-cloudberry.yml +++ b/.github/workflows/build-cloudberry.yml @@ -1456,6 +1456,9 @@ jobs: echo "=== Executing Behave test ===" config_log="build-logs/details/make-${{ matrix.test }}-config$i.log" + echo "Installing OS dependencies for Behave..." + dnf install -y libffi-devel || echo "Warning: failed to install libffi-devel" + echo "Installing Python dependencies for Behave..." su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" From 150a8ac6e0ba4c0be3c03f19c5a8fe659b9e9505 Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Thu, 26 Mar 2026 15:45:21 +0800 Subject: [PATCH 3/8] Fix --- .github/workflows/build-cloudberry.yml | 72 ++++++++++++++++--- .../behave/mgmt_utils/gprecoverseg.feature | 2 +- .../behave/mgmt_utils/steps/mgmt_utils.py | 13 ---- .../mgmt_utils/steps/recoverseg_mgmt_utils.py | 57 ++++++++++++++- 4 files changed, 118 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-cloudberry.yml b/.github/workflows/build-cloudberry.yml index 94927c90732..9a01e18dc7d 100644 --- a/.github/workflows/build-cloudberry.yml +++ b/.github/workflows/build-cloudberry.yml @@ -223,6 +223,7 @@ jobs: DEFAULT_ENABLE_CGROUPS=false DEFAULT_ENABLE_CORE_CHECK=true DEFAULT_PG_SETTINGS_OPTIMIZER="" + DEFAULT_TEST_KIND="installcheck" # Define base test configurations ALL_TESTS='{ @@ -341,6 +342,7 @@ jobs: }, {"test":"ic-behave", "make_configs":["gpMgmt/:behave"], + "test_kind":"behave", "enable_core_check":false } ] @@ -352,11 +354,13 @@ jobs: --argjson ec "$DEFAULT_ENABLE_CGROUPS" \ --argjson ecc "$DEFAULT_ENABLE_CORE_CHECK" \ --arg opt "$DEFAULT_PG_SETTINGS_OPTIMIZER" \ + --arg tk "$DEFAULT_TEST_KIND" \ 'def get_defaults: { num_primary_mirror_pairs: ($npm|tonumber), enable_cgroups: $ec, enable_core_check: $ecc, + test_kind: $tk, pg_settings: { optimizer: $opt } @@ -1452,7 +1456,7 @@ jobs: config="${configs[$i]}" IFS=':' read -r dir target <<< "$config" - if [ "${{ matrix.test }}" == "ic-behave" ]; then + if [ "${{ matrix.test_kind }}" == "behave" ]; then echo "=== Executing Behave test ===" config_log="build-logs/details/make-${{ matrix.test }}-config$i.log" @@ -1462,7 +1466,7 @@ jobs: echo "Installing Python dependencies for Behave..." su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" - if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && make -f Makefile.behave behave" \ + if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave test/behave/mgmt_utils/" \ 2>&1 | tee -a "$config_log"; then echo "::warning::Test execution failed for Behave" overall_status=1 @@ -1597,16 +1601,53 @@ jobs: fi # Parse this configuration's results + if [ "${{ matrix.test_kind }}" == "behave" ]; then + scenarios_line=$(grep -E '^[0-9]+ scenarios passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ untested$' "$config_log" | tail -n 1) + features_line=$(grep -E '^[0-9]+ features passed, [0-9]+ failed, [0-9]+ skipped$' "$config_log" | tail -n 1) + steps_line=$(grep -E '^[0-9]+ steps passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ undefined, [0-9]+ untested$' "$config_log" | tail -n 1) - MAKE_NAME="${{ matrix.test }}-config$i" \ - "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/parse-test-results.sh "$config_log" - status_code=$? + if [[ -z "$scenarios_line" ]]; then + status_code=2 + else + read -r scenarios_passed scenarios_failed scenarios_skipped scenarios_untested <<< "$(echo "$scenarios_line" | sed -E 's/^([0-9]+) scenarios passed, ([0-9]+) failed, ([0-9]+) skipped, ([0-9]+) untested$/\1 \2 \3 \4/')" + total_scenarios=$((scenarios_passed + scenarios_failed + scenarios_skipped)) - { - echo "SUITE_NAME=${{ matrix.test }}" - echo "DIR=${dir}" - echo "TARGET=${target}" - } >> test_results.txt + { + if [[ "$scenarios_failed" -eq 0 ]]; then + echo "STATUS=passed" + else + echo "STATUS=failed" + fi + echo "TOTAL_TESTS=${total_scenarios}" + echo "FAILED_TESTS=${scenarios_failed}" + echo "PASSED_TESTS=${scenarios_passed}" + echo "IGNORED_TESTS=${scenarios_skipped}" + echo "BEHAVE_UNTESTED_SCENARIOS=${scenarios_untested}" + echo "BEHAVE_FEATURES_SUMMARY=\"${features_line:-unavailable}\"" + echo "BEHAVE_SCENARIOS_SUMMARY=\"${scenarios_line}\"" + echo "BEHAVE_STEPS_SUMMARY=\"${steps_line:-unavailable}\"" + echo "SUITE_NAME=${{ matrix.test }}" + echo "DIR=${dir}" + echo "TARGET=${target}" + } > test_results.txt + + if [[ "$scenarios_failed" -eq 0 ]]; then + status_code=0 + else + status_code=1 + fi + fi + else + MAKE_NAME="${{ matrix.test }}-config$i" \ + "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/parse-test-results.sh "$config_log" + status_code=$? + + { + echo "SUITE_NAME=${{ matrix.test }}" + echo "DIR=${dir}" + echo "TARGET=${target}" + } >> test_results.txt + fi # Process return code case $status_code in @@ -1810,6 +1851,17 @@ jobs: echo "| Failed Tests | ${FAILED_TESTS:-0} |" echo "| Ignored Tests | ${IGNORED_TESTS:-0} |" + if [[ -n "${BEHAVE_SCENARIOS_SUMMARY:-}" ]]; then + echo "" + echo "#### Behave Summary" + echo "| Metric | Summary |" + echo "|--------|---------|" + echo "| Features | ${BEHAVE_FEATURES_SUMMARY:-unavailable} |" + echo "| Scenarios | ${BEHAVE_SCENARIOS_SUMMARY} |" + echo "| Steps | ${BEHAVE_STEPS_SUMMARY:-unavailable} |" + echo "| Untested Scenarios | ${BEHAVE_UNTESTED_SCENARIOS:-0} |" + fi + # Add failed tests if any if [[ -n "${FAILED_TEST_NAMES:-}" && "${FAILED_TESTS:-0}" != "0" ]]; then echo "" diff --git a/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature b/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature index 3d28dfc11d5..1f45c89c5b0 100644 --- a/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature +++ b/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature @@ -1,7 +1,7 @@ @gprecoverseg Feature: gprecoverseg tests - Scenario: incremental recovery works with tablespaces + Scenario Outline: incremental recovery works with tablespaces Given the database is running And a tablespace is created with data And user stops all primary processes diff --git a/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py b/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py index 5af0e37762e..45e888dbcd4 100644 --- a/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py +++ b/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py @@ -489,14 +489,6 @@ def impl(context): else: return -@then( 'verify if the gprecoverseg.lock directory is present in coordinator_data_directory') -def impl(context): - gprecoverseg_lock_file = "%s/gprecoverseg.lock" % gp.get_coordinatordatadir() - if not os.path.exists(gprecoverseg_lock_file): - raise Exception('gprecoverseg.lock directory does not exist') - else: - return - @then('verify that lines from recovery_progress.file are present in segment progress files in {logdir}') def impl(context, logdir): @@ -671,11 +663,6 @@ def impl(context, process_name, signal_name): command = "ps ux | grep bin/{0} | awk '{{print $2}}' | xargs kill -{1}".format(process_name, sig.value) run_async_command(context, command) -@when('the user asynchronously sets up to end {process_name} process with SIGHUP') -def impl(context, process_name): - command = "ps ux | grep bin/%s | awk '{print $2}' | xargs kill -9" % (process_name) - run_async_command(context, command) - @when('the user asynchronously sets up to end gpcreateseg process when it starts') def impl(context): # We keep trying to find the gpcreateseg process using ps,grep diff --git a/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py b/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py index 7357b614172..207d31a2e4b 100644 --- a/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py +++ b/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py @@ -4,15 +4,18 @@ from time import sleep from contextlib import closing +from gppylib import gplog from gppylib.commands.base import Command, ExecutionError, REMOTE, WorkerPool +from gppylib.commands.gp import RECOVERY_REWIND_APPNAME from gppylib.db import dbconn +from gppylib.db.catalog import RemoteQueryCommand from gppylib.gparray import GpArray, ROLE_PRIMARY, ROLE_MIRROR -from gppylib.programs.clsRecoverSegment_triples import get_segments_with_running_basebackup, is_pg_rewind_running -from gppylib.operations.get_segments_in_recovery import is_seg_in_backup_mode from test.behave_utils.utils import * import platform, shutil from behave import given, when, then +logger = gplog.get_default_logger() + #TODO remove duplication of these functions def _get_gpAdminLogs_directory(): return "%s/gpAdminLogs" % os.path.expanduser("~") @@ -23,6 +26,56 @@ def lines_matching_both(in_str, str_1, str_2): return [line for line in lines if line.count(str_1) and line.count(str_2)] +def get_segments_with_running_basebackup(): + sql = "select gp_segment_id from gp_stat_replication where application_name = 'pg_basebackup'" + + try: + with closing(dbconn.connect(dbconn.DbURL())) as conn: + rows = dbconn.query(conn, sql).fetchall() + except Exception as e: + raise Exception("Failed to query gp_stat_replication: %s" % str(e)) + + return {row[0] for row in rows} + + +def is_pg_rewind_running(hostname, port): + sql = "SELECT count(*) FROM pg_stat_activity WHERE application_name = '{}'".format( + RECOVERY_REWIND_APPNAME + ) + + try: + url = dbconn.DbURL(hostname=hostname, port=port, dbname='template1') + with closing(dbconn.connect(url, utility=True)) as conn: + return dbconn.querySingleton(conn, sql) > 0 + except Exception as e: + raise Exception( + "Failed to query pg_stat_activity for segment hostname: {}, port: {}, error: {}".format( + hostname, str(port), str(e) + ) + ) + + +def is_seg_in_backup_mode(hostname, port): + logger.debug( + "Checking if backup is already in progress for the source server with host {} and port {}".format( + hostname, port + ) + ) + + try: + query_cmd = RemoteQueryCommand("pg_is_in_backup", "SELECT pg_is_in_backup()", hostname, port) + query_cmd.run() + res = query_cmd.get_results() + except Exception as e: + raise Exception( + "Failed to query pg_is_in_backup() for segment with hostname {}, port {}, error: {}".format( + hostname, str(port), str(e) + ) + ) + + return res[0][0] + + @given('the information of contents {contents} is saved') @when('the information of contents {contents} is saved') @then('the information of contents {contents} is saved') From a1dc67a879f2a649edaa663ba7ab36f866a3dc23 Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Thu, 26 Mar 2026 16:26:34 +0800 Subject: [PATCH 4/8] fix --- .../mgmt_utils/steps/recoverseg_mgmt_utils.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py b/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py index 207d31a2e4b..12e3f5abe49 100644 --- a/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py +++ b/gpMgmt/test/behave/mgmt_utils/steps/recoverseg_mgmt_utils.py @@ -8,7 +8,6 @@ from gppylib.commands.base import Command, ExecutionError, REMOTE, WorkerPool from gppylib.commands.gp import RECOVERY_REWIND_APPNAME from gppylib.db import dbconn -from gppylib.db.catalog import RemoteQueryCommand from gppylib.gparray import GpArray, ROLE_PRIMARY, ROLE_MIRROR from test.behave_utils.utils import * import platform, shutil @@ -27,6 +26,10 @@ def lines_matching_both(in_str, str_1, str_2): def get_segments_with_running_basebackup(): + """ + Returns a set of content ids whose source segments currently have + a running pg_basebackup. + """ sql = "select gp_segment_id from gp_stat_replication where application_name = 'pg_basebackup'" try: @@ -35,10 +38,18 @@ def get_segments_with_running_basebackup(): except Exception as e: raise Exception("Failed to query gp_stat_replication: %s" % str(e)) - return {row[0] for row in rows} + segments_with_running_basebackup = {row[0] for row in rows} + + if len(segments_with_running_basebackup) == 0: + logger.debug("No basebackup running") + + return segments_with_running_basebackup def is_pg_rewind_running(hostname, port): + """ + Returns true if a pg_rewind process is running for the given segment. + """ sql = "SELECT count(*) FROM pg_stat_activity WHERE application_name = '{}'".format( RECOVERY_REWIND_APPNAME ) @@ -56,16 +67,24 @@ def is_pg_rewind_running(hostname, port): def is_seg_in_backup_mode(hostname, port): + """ + Returns true if the source segment is already in backup mode. + + Differential recovery uses pg_start_backup() on the source segment, so + a source that is already in backup mode indicates differential recovery + may already be in progress. + """ logger.debug( "Checking if backup is already in progress for the source server with host {} and port {}".format( hostname, port ) ) + sql = "SELECT pg_is_in_backup()" try: - query_cmd = RemoteQueryCommand("pg_is_in_backup", "SELECT pg_is_in_backup()", hostname, port) - query_cmd.run() - res = query_cmd.get_results() + url = dbconn.DbURL(hostname=hostname, port=port, dbname='template1') + with closing(dbconn.connect(url, utility=True)) as conn: + res = dbconn.querySingleton(conn, sql) except Exception as e: raise Exception( "Failed to query pg_is_in_backup() for segment with hostname {}, port {}, error: {}".format( @@ -73,7 +92,7 @@ def is_seg_in_backup_mode(hostname, port): ) ) - return res[0][0] + return res @given('the information of contents {contents} is saved') From a51164c1e2ff03a86221e8b03d157b483f34e430 Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Thu, 26 Mar 2026 18:21:48 +0800 Subject: [PATCH 5/8] Split Behave CI into isolated feature groups --- .github/workflows/build-cloudberry.yml | 68 +++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-cloudberry.yml b/.github/workflows/build-cloudberry.yml index 9a01e18dc7d..f582f13a2a1 100644 --- a/.github/workflows/build-cloudberry.yml +++ b/.github/workflows/build-cloudberry.yml @@ -340,9 +340,67 @@ jobs: {"test":"ic-cbdb-parallel", "make_configs":["src/test/regress:installcheck-cbdb-parallel"] }, - {"test":"ic-behave", + {"test":"ic-behave-state", "make_configs":["gpMgmt/:behave"], "test_kind":"behave", + "behave_features":[ + "test/behave/mgmt_utils/analyzedb.feature", + "test/behave/mgmt_utils/gp_bash_functions.feature", + "test/behave/mgmt_utils/gpcheckcat.feature", + "test/behave/mgmt_utils/gpcheckperf.feature", + "test/behave/mgmt_utils/gpconfig.feature", + "test/behave/mgmt_utils/minirepro.feature", + "test/behave/mgmt_utils/gpstart.feature", + "test/behave/mgmt_utils/gpstate.feature", + "test/behave/mgmt_utils/gpstop.feature" + ], + "enable_core_check":false + }, + {"test":"ic-behave-init", + "make_configs":["gpMgmt/:behave"], + "test_kind":"behave", + "behave_features":[ + "test/behave/mgmt_utils/gpactivatestandby.feature", + "test/behave/mgmt_utils/gpinitsystem.feature", + "test/behave/mgmt_utils/gpinitstandby.feature" + ], + "enable_core_check":false + }, + {"test":"ic-behave-mirror", + "make_configs":["gpMgmt/:behave"], + "test_kind":"behave", + "behave_features":[ + "test/behave/mgmt_utils/gpaddmirrors.feature", + "test/behave/mgmt_utils/gpmovemirrors.feature", + "test/behave/mgmt_utils/gprecoverseg.feature", + "test/behave/mgmt_utils/gpreload.feature", + "test/behave/mgmt_utils/replication_slots.feature" + ], + "enable_core_check":false + }, + {"test":"ic-behave-expand", + "make_configs":["gpMgmt/:behave"], + "test_kind":"behave", + "behave_features":[ + "test/behave/mgmt_utils/gpexpand.feature" + ], + "enable_core_check":false + }, + {"test":"ic-behave-package", + "make_configs":["gpMgmt/:behave"], + "test_kind":"behave", + "behave_features":[ + "test/behave/mgmt_utils/gppkg.feature" + ], + "enable_core_check":false + }, + {"test":"ic-behave-ssh", + "make_configs":["gpMgmt/:behave"], + "test_kind":"behave", + "behave_features":[ + "test/behave/mgmt_utils/gpssh.feature", + "test/behave/mgmt_utils/gpssh_exkeys.feature" + ], "enable_core_check":false } ] @@ -1459,14 +1517,20 @@ jobs: if [ "${{ matrix.test_kind }}" == "behave" ]; then echo "=== Executing Behave test ===" config_log="build-logs/details/make-${{ matrix.test }}-config$i.log" + behave_targets="${{ join(matrix.behave_features, ' ') }}" echo "Installing OS dependencies for Behave..." dnf install -y libffi-devel || echo "Warning: failed to install libffi-devel" echo "Installing Python dependencies for Behave..." su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" + + echo "Behave feature group:" + for feature in $behave_targets; do + echo "- $feature" + done - if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave test/behave/mgmt_utils/" \ + if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave $behave_targets" \ 2>&1 | tee -a "$config_log"; then echo "::warning::Test execution failed for Behave" overall_status=1 From c754c285f3021343b2cf2ac300354ee0403ab0d7 Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 27 Mar 2026 15:48:17 +0800 Subject: [PATCH 6/8] Add new behave workflow --- .github/workflows/behave-cloudberry.yml | 694 ++++++++++++++++++++++++ .github/workflows/build-cloudberry.yml | 158 +----- 2 files changed, 702 insertions(+), 150 deletions(-) create mode 100644 .github/workflows/behave-cloudberry.yml diff --git a/.github/workflows/behave-cloudberry.yml b/.github/workflows/behave-cloudberry.yml new file mode 100644 index 00000000000..71a9d0b0803 --- /dev/null +++ b/.github/workflows/behave-cloudberry.yml @@ -0,0 +1,694 @@ +# +# -------------------------------------------------------------------- +# +# 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. +# +# -------------------------------------------------------------------- +# GitHub Actions Workflow: Apache Cloudberry Behave Pipeline +# -------------------------------------------------------------------- +# Description: +# +# This workflow runs Apache Cloudberry gpMgmt Behave tests on Rocky Linux 9. +# It is intentionally separated from the main build/installcheck workflow so +# that Behave-specific matrix expansion, environment setup, result parsing, +# and iterative test stabilization do not disturb the primary CI path. +# +# Workflow Overview: +# 1. **Prepare Behave Matrix**: +# - Expands the selected Behave command-level test matrix. +# - Supports manual filtering through `test_selection`. +# +# 2. **Build Job**: +# - Builds Apache Cloudberry and creates source/RPM artifacts for reuse +# within this workflow. +# +# 3. **Behave Job (Matrix)**: +# - Creates a demo cluster for each Behave matrix entry. +# - Runs the selected gpMgmt feature file(s) in isolation. +# - Parses Behave summaries and uploads logs/metadata artifacts. +# +# 4. **Report Job**: +# - Aggregates build and Behave job status into a final workflow summary. +# +# Execution Environment: +# - **Runs On**: ubuntu-22.04 with Rocky Linux 9 containers. +# - **Primary Test Scope**: `gpMgmt/test/behave/mgmt_utils` +# +# Notes: +# - Trigger mode: manual `workflow_dispatch` only. +# - Behave tests are split by command to reduce cross-feature environment +# pollution. +# - This workflow currently focuses on single-host CI-compatible Behave tests. +# - Logs and parsed summaries are uploaded as artifacts for each matrix entry. +# -------------------------------------------------------------------- + +name: Apache Cloudberry Behave + +on: + workflow_dispatch: + inputs: + test_selection: + description: 'Select Behave tests to run (comma-separated). Examples: ic-behave-gpconfig,ic-behave-gpstart' + required: false + default: 'all' + type: string + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + packages: read + actions: write + checks: read + pull-requests: read + +env: + LOG_RETENTION_DAYS: 7 + ENABLE_DEBUG: false + +jobs: + prepare-behave-matrix: + runs-on: ubuntu-22.04 + outputs: + behave-matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - id: set-matrix + run: | + echo "=== Behave Matrix Preparation Diagnostics ===" + echo "Event type: ${{ github.event_name }}" + echo "Test selection input: '${{ github.event.inputs.test_selection }}'" + + ALL_BEHAVE_TESTS='{ + "include": [ + {"test":"ic-behave-analyzedb","behave_features":["test/behave/mgmt_utils/analyzedb.feature"]}, + {"test":"ic-behave-gp-bash-functions","behave_features":["test/behave/mgmt_utils/gp_bash_functions.feature"]}, + {"test":"ic-behave-gpactivatestandby","behave_features":["test/behave/mgmt_utils/gpactivatestandby.feature"]}, + {"test":"ic-behave-gpaddmirrors","behave_features":["test/behave/mgmt_utils/gpaddmirrors.feature"]}, + {"test":"ic-behave-gpcheckcat","behave_features":["test/behave/mgmt_utils/gpcheckcat.feature"]}, + {"test":"ic-behave-gpcheckperf","behave_features":["test/behave/mgmt_utils/gpcheckperf.feature"]}, + {"test":"ic-behave-gpconfig","behave_features":["test/behave/mgmt_utils/gpconfig.feature"]}, + {"test":"ic-behave-gpinitstandby","behave_features":["test/behave/mgmt_utils/gpinitstandby.feature"]}, + {"test":"ic-behave-gpinitsystem","behave_features":["test/behave/mgmt_utils/gpinitsystem.feature"]}, + {"test":"ic-behave-gpmovemirrors","behave_features":["test/behave/mgmt_utils/gpmovemirrors.feature"]}, + {"test":"ic-behave-gprecoverseg","behave_features":["test/behave/mgmt_utils/gprecoverseg.feature"]}, + {"test":"ic-behave-gpreload","behave_features":["test/behave/mgmt_utils/gpreload.feature"]}, + {"test":"ic-behave-gpstart","behave_features":["test/behave/mgmt_utils/gpstart.feature"]}, + {"test":"ic-behave-gpstate","behave_features":["test/behave/mgmt_utils/gpstate.feature"]}, + {"test":"ic-behave-gpstop","behave_features":["test/behave/mgmt_utils/gpstop.feature"]}, + {"test":"ic-behave-gpssh","behave_features":["test/behave/mgmt_utils/gpssh.feature"]}, + {"test":"ic-behave-minirepro","behave_features":["test/behave/mgmt_utils/minirepro.feature"]}, + {"test":"ic-behave-replication-slots","behave_features":["test/behave/mgmt_utils/replication_slots.feature"]} + ] + }' + + VALID_TESTS=$(echo "$ALL_BEHAVE_TESTS" | jq -r '.include[].test') + IFS=',' read -ra SELECTED_TESTS <<< "${{ github.event.inputs.test_selection }}" + + if [[ "${SELECTED_TESTS[*]}" == "all" || -z "${SELECTED_TESTS[*]}" ]]; then + mapfile -t SELECTED_TESTS <<< "$VALID_TESTS" + fi + + INVALID_TESTS=() + FILTERED_TESTS=() + for TEST in "${SELECTED_TESTS[@]}"; do + TEST=$(echo "$TEST" | tr -d '[:space:]') + if echo "$VALID_TESTS" | grep -qw "$TEST"; then + FILTERED_TESTS+=("$TEST") + else + INVALID_TESTS+=("$TEST") + fi + done + + if [[ ${#INVALID_TESTS[@]} -gt 0 ]]; then + echo "::error::Invalid Behave test(s) selected: ${INVALID_TESTS[*]}" + echo "Valid tests are: $(echo "$VALID_TESTS" | tr '\n' ', ')" + exit 1 + fi + + RESULT='{"include":[' + FIRST=true + for TEST in "${FILTERED_TESTS[@]}"; do + CONFIG=$(jq -c --arg test "$TEST" '.include[] | select(.test == $test)' <<< "$ALL_BEHAVE_TESTS") + if [[ "$FIRST" == true ]]; then + FIRST=false + else + RESULT="${RESULT}," + fi + RESULT="${RESULT}${CONFIG}" + done + RESULT="${RESULT}]}" + + echo "Final behave matrix configuration:" + echo "$RESULT" | jq . + + { + echo "matrix<> "$GITHUB_OUTPUT" + + build: + name: Build Apache Cloudberry RPM + env: + JOB_TYPE: build + runs-on: ubuntu-22.04 + timeout-minutes: 120 + outputs: + build_timestamp: ${{ steps.set_timestamp.outputs.timestamp }} + container: + image: apache/incubator-cloudberry:cbdb-build-rocky9-latest + options: >- + --user root + -h cdw + -v /usr/share:/host_usr_share + -v /usr/local:/host_usr_local + -v /opt:/host_opt + steps: + - name: Free Disk Space + run: | + echo "=== Disk space before cleanup ===" + df -h / + + rm -rf /host_opt/hostedtoolcache || true + rm -rf /host_usr_local/lib/android || true + rm -rf /host_usr_share/dotnet || true + rm -rf /host_opt/ghc || true + rm -rf /host_usr_local/.ghcup || true + rm -rf /host_usr_share/swift || true + rm -rf /host_usr_local/share/powershell || true + rm -rf /host_usr_local/share/chromium || true + rm -rf /host_usr_share/miniconda || true + rm -rf /host_opt/az || true + rm -rf /host_usr_share/sbt || true + + echo "=== Disk space after cleanup ===" + df -h / + + - name: Set build timestamp + id: set_timestamp + run: | + timestamp=$(date +'%Y%m%d_%H%M%S') + echo "timestamp=$timestamp" | tee -a "$GITHUB_OUTPUT" + echo "BUILD_TIMESTAMP=$timestamp" | tee -a "$GITHUB_ENV" + + - name: Checkout Apache Cloudberry + uses: actions/checkout@v4 + with: + fetch-depth: 1 + submodules: true + + - name: Cloudberry Environment Initialization + env: + LOGS_DIR: build-logs + run: | + set -eo pipefail + if ! su - gpadmin -c "/tmp/init_system.sh"; then + echo "::error::Container initialization failed" + exit 1 + fi + + mkdir -p "${LOGS_DIR}/details" + chown -R gpadmin:gpadmin . + chmod -R 755 . + chmod 777 "${LOGS_DIR}" + + df -kh / + rm -rf /__t/* + df -kh / + + df -h | tee -a "${LOGS_DIR}/details/disk-usage.log" + free -h | tee -a "${LOGS_DIR}/details/memory-usage.log" + + { + echo "=== Environment Information ===" + uname -a + df -h + free -h + env + } | tee -a "${LOGS_DIR}/details/environment.log" + + echo "SRC_DIR=${GITHUB_WORKSPACE}" | tee -a "$GITHUB_ENV" + + - name: Run Apache Cloudberry configure script + env: + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + chmod +x "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh + if ! time su - gpadmin -c "cd ${SRC_DIR} && SRC_DIR=${SRC_DIR} ENABLE_DEBUG=${{ env.ENABLE_DEBUG }} ${SRC_DIR}/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh"; then + echo "::error::Configure script failed" + exit 1 + fi + + - name: Run Apache Cloudberry build script + env: + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + chmod +x "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/build-cloudberry.sh + if ! time su - gpadmin -c "cd ${SRC_DIR} && SRC_DIR=${SRC_DIR} ${SRC_DIR}/devops/build/automation/cloudberry/scripts/build-cloudberry.sh"; then + echo "::error::Build script failed" + exit 1 + fi + + - name: Create Source tarball, create RPM and verify artifacts + env: + CBDB_VERSION: 99.0.0 + BUILD_NUMBER: 1 + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + + tar czf "${SRC_DIR}"/../apache-cloudberry-incubating-src.tgz -C "${SRC_DIR}"/.. ./cloudberry + mv "${SRC_DIR}"/../apache-cloudberry-incubating-src.tgz "${SRC_DIR}" + + rpmdev-setuptree + ln -s "${SRC_DIR}"/devops/build/packaging/rpm/apache-cloudberry-db-incubating.spec "${HOME}"/rpmbuild/SPECS/apache-cloudberry-db-incubating.spec + cp "${SRC_DIR}"/LICENSE /usr/local/cloudberry-db + + DEBUG_RPMBUILD_OPT="" + DEBUG_IDENTIFIER="" + if [ "${{ env.ENABLE_DEBUG }}" = "true" ]; then + DEBUG_RPMBUILD_OPT="--with-debug" + DEBUG_IDENTIFIER=".debug" + fi + + "${SRC_DIR}"/devops/build/packaging/rpm/build-rpm.sh --version "${CBDB_VERSION}" --release "${BUILD_NUMBER}" "${DEBUG_RPMBUILD_OPT}" + + os_version=$(grep -oP '(?<=^VERSION_ID=")[0-9]' /etc/os-release) + RPM_FILE="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm + cp "${RPM_FILE}" "${SRC_DIR}" + RPM_DEBUG="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-debuginfo-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm + cp "${RPM_DEBUG}" "${SRC_DIR}" + + - name: Upload build logs + uses: actions/upload-artifact@v4 + with: + name: behave-build-logs-${{ env.BUILD_TIMESTAMP }} + path: | + build-logs/ + retention-days: ${{ env.LOG_RETENTION_DAYS }} + + - name: Upload Cloudberry RPM build artifacts + uses: actions/upload-artifact@v4 + with: + name: apache-cloudberry-db-incubating-rpm-build-artifacts + retention-days: ${{ env.LOG_RETENTION_DAYS }} + if-no-files-found: error + path: | + *.rpm + + - name: Upload Cloudberry source build artifacts + uses: actions/upload-artifact@v4 + with: + name: apache-cloudberry-db-incubating-source-build-artifacts + retention-days: ${{ env.LOG_RETENTION_DAYS }} + if-no-files-found: error + path: | + apache-cloudberry-incubating-src.tgz + + behave: + name: ${{ matrix.test }} + needs: [build, prepare-behave-matrix] + if: | + !cancelled() && + needs.build.result == 'success' + runs-on: ubuntu-22.04 + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.prepare-behave-matrix.outputs.behave-matrix) }} + container: + image: apache/incubator-cloudberry:cbdb-build-rocky9-latest + options: >- + --privileged + --user root + --hostname cdw + --shm-size=2gb + --ulimit core=-1 + --cgroupns=host + -v /sys/fs/cgroup:/sys/fs/cgroup:rw + -v /usr/share:/host_usr_share + -v /usr/local:/host_usr_local + -v /opt:/host_opt + steps: + - name: Free Disk Space + run: | + echo "=== Disk space before cleanup ===" + df -h / + + rm -rf /host_opt/hostedtoolcache || true + rm -rf /host_usr_local/lib/android || true + rm -rf /host_usr_share/dotnet || true + rm -rf /host_opt/ghc || true + rm -rf /host_usr_local/.ghcup || true + rm -rf /host_usr_share/swift || true + rm -rf /host_usr_local/share/powershell || true + rm -rf /host_usr_local/share/chromium || true + rm -rf /host_usr_share/miniconda || true + rm -rf /host_opt/az || true + rm -rf /host_usr_share/sbt || true + + echo "=== Disk space after cleanup ===" + df -h / + + - name: Cloudberry Environment Initialization + env: + LOGS_DIR: build-logs + run: | + set -eo pipefail + if ! su - gpadmin -c "/tmp/init_system.sh"; then + echo "::error::Container initialization failed" + exit 1 + fi + + mkdir -p "${LOGS_DIR}/details" + chown -R gpadmin:gpadmin . + chmod -R 755 . + chmod 777 "${LOGS_DIR}" + + df -kh / + rm -rf /__t/* + df -kh / + + df -h | tee -a "${LOGS_DIR}/details/disk-usage.log" + free -h | tee -a "${LOGS_DIR}/details/memory-usage.log" + + { + echo "=== Environment Information ===" + uname -a + df -h + free -h + env + } | tee -a "${LOGS_DIR}/details/environment.log" + + echo "SRC_DIR=${GITHUB_WORKSPACE}" | tee -a "$GITHUB_ENV" + + - name: Generate Behave Job Summary Start + if: always() + run: | + { + echo "# Behave Job Summary: ${{ matrix.test }}" + echo "## Environment" + echo "- Start Time: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + echo "- OS Version: $(cat /etc/redhat-release)" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Download Cloudberry RPM build artifacts + uses: actions/download-artifact@v4 + with: + name: apache-cloudberry-db-incubating-rpm-build-artifacts + path: ${{ github.workspace }}/rpm_build_artifacts + merge-multiple: false + run-id: ${{ github.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download Cloudberry Source build artifacts + uses: actions/download-artifact@v4 + with: + name: apache-cloudberry-db-incubating-source-build-artifacts + path: ${{ github.workspace }}/source_build_artifacts + merge-multiple: false + run-id: ${{ github.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify downloaded artifacts + id: verify-artifacts + run: | + set -eo pipefail + + SRC_TARBALL_FILE=$(ls "${GITHUB_WORKSPACE}"/source_build_artifacts/apache-cloudberry-incubating-src.tgz) + if [ ! -f "${SRC_TARBALL_FILE}" ]; then + echo "::error::SRC TARBALL file not found" + exit 1 + fi + echo "src_tarball_file=${SRC_TARBALL_FILE}" >> "$GITHUB_OUTPUT" + + RPM_FILE=$(ls "${GITHUB_WORKSPACE}"/rpm_build_artifacts/apache-cloudberry-db-incubating-[0-9]*.rpm | grep -v "debuginfo") + if [ ! -f "${RPM_FILE}" ]; then + echo "::error::RPM file not found" + exit 1 + fi + echo "rpm_file=${RPM_FILE}" >> "$GITHUB_OUTPUT" + + - name: Install Cloudberry RPM + if: success() + env: + RPM_FILE: ${{ steps.verify-artifacts.outputs.rpm_file }} + run: | + set -eo pipefail + + dnf clean all + dnf makecache --refresh || dnf makecache + rm -rf /usr/local/cloudberry-db + + if ! time dnf install -y --setopt=retries=10 --releasever=9 "${RPM_FILE}"; then + echo "::error::RPM installation failed" + exit 1 + fi + + rm -rf "${GITHUB_WORKSPACE}"/rpm_build_artifacts + + - name: Extract source tarball + if: success() + env: + SRC_TARBALL_FILE: ${{ steps.verify-artifacts.outputs.src_tarball_file }} + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + + if ! time tar zxf "${SRC_TARBALL_FILE}" -C "${SRC_DIR}"/.. ; then + echo "::error::Source extraction failed" + exit 1 + fi + + rm -rf "${GITHUB_WORKSPACE}"/source_build_artifacts + + - name: Create Apache Cloudberry demo cluster + if: success() + env: + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + chmod +x "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/create-cloudberry-demo-cluster.sh + + if ! time su - gpadmin -c "cd ${SRC_DIR} && NUM_PRIMARY_MIRROR_PAIRS='3' SRC_DIR=${SRC_DIR} ${SRC_DIR}/devops/build/automation/cloudberry/scripts/create-cloudberry-demo-cluster.sh"; then + echo "::error::Demo cluster creation failed" + exit 1 + fi + + - name: Run Behave Tests + if: success() + env: + SRC_DIR: ${{ github.workspace }} + shell: bash {0} + run: | + set -o pipefail + + mkdir -p build-logs/details + config_log="build-logs/details/make-${{ matrix.test }}-config0.log" + behave_targets="${{ join(matrix.behave_features, ' ') }}" + + mkdir -p "/tmp/cloudberry-cores" + chmod 1777 "/tmp/cloudberry-cores" + sysctl -w kernel.core_pattern="/tmp/cloudberry-cores/core-%e-%s-%u-%g-%p-%t" + + sudo dnf install -y libffi-devel || echo "Warning: failed to install libffi-devel" + su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" + + echo "Running features:" + for feature in $behave_targets; do + echo "- $feature" + done + + if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave $behave_targets" \ + 2>&1 | tee -a "$config_log"; then + echo "::warning::Behave execution reported failures" + exit 1 + fi + + - name: Parse Behave Results + if: always() + shell: bash {0} + run: | + set -o pipefail + + config_log="build-logs/details/make-${{ matrix.test }}-config0.log" + if [ ! -f "$config_log" ]; then + { + echo "MAKE_COMMAND=\"behave ${{ join(matrix.behave_features, ' ') }}\"" + echo "STATUS=missing_log" + echo "TOTAL_TESTS=0" + echo "FAILED_TESTS=0" + echo "PASSED_TESTS=0" + echo "IGNORED_TESTS=0" + } | tee "test_results.${{ matrix.test }}.0.txt" + exit 1 + fi + + features_line=$(grep -E '^[0-9]+ features passed, [0-9]+ failed, [0-9]+ skipped$' "$config_log" | tail -n 1) + scenarios_line=$(grep -E '^[0-9]+ scenarios passed, [0-9]+ failed, [0-9]+ skipped(, [0-9]+ untested)?$' "$config_log" | tail -n 1) + steps_line=$(grep -E '^[0-9]+ steps passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ undefined(, [0-9]+ untested)?$' "$config_log" | tail -n 1) + + if [[ -z "$scenarios_line" ]]; then + { + echo "MAKE_COMMAND=\"behave ${{ join(matrix.behave_features, ' ') }}\"" + echo "STATUS=parse_error" + echo "TOTAL_TESTS=0" + echo "FAILED_TESTS=0" + echo "PASSED_TESTS=0" + echo "IGNORED_TESTS=0" + } | tee "test_results.${{ matrix.test }}.0.txt" + exit 1 + fi + + scenario_counts=$(echo "$scenarios_line" | sed -E 's/^([0-9]+) scenarios passed, ([0-9]+) failed, ([0-9]+) skipped(, ([0-9]+) untested)?$/\1 \2 \3 \5/') + read -r scenarios_passed scenarios_failed scenarios_skipped scenarios_untested <<< "$scenario_counts" + scenarios_untested=${scenarios_untested:-0} + total_scenarios=$((scenarios_passed + scenarios_failed + scenarios_skipped)) + + { + echo "MAKE_COMMAND=\"behave ${{ join(matrix.behave_features, ' ') }}\"" + if [[ "$scenarios_failed" -eq 0 ]]; then + echo "STATUS=passed" + else + echo "STATUS=failed" + fi + echo "TOTAL_TESTS=${total_scenarios}" + echo "FAILED_TESTS=${scenarios_failed}" + echo "PASSED_TESTS=${scenarios_passed}" + echo "IGNORED_TESTS=${scenarios_skipped}" + echo "BEHAVE_UNTESTED_SCENARIOS=${scenarios_untested}" + echo "BEHAVE_FEATURES_SUMMARY=\"${features_line:-unavailable}\"" + echo "BEHAVE_SCENARIOS_SUMMARY=\"${scenarios_line}\"" + echo "BEHAVE_STEPS_SUMMARY=\"${steps_line:-unavailable}\"" + } | tee "test_results.${{ matrix.test }}.0.txt" + + if [[ "$scenarios_failed" -eq 0 ]]; then + exit 0 + fi + exit 1 + + - name: Generate Behave Job Summary End + if: always() + shell: bash {0} + run: | + { + echo "## Test Results" + echo "- End Time: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + + if [[ ! -f "test_results.${{ matrix.test }}.0.txt" ]]; then + echo "### Result Status" + echo "⚠️ No results file found" + exit 0 + fi + + . "test_results.${{ matrix.test }}.0.txt" + + echo "### Command" + echo "\`$MAKE_COMMAND\`" + echo "" + + echo "### Status" + case "${STATUS:-unknown}" in + passed) + echo "✅ All scenarios passed" + ;; + failed) + echo "❌ Some scenarios failed" + ;; + parse_error) + echo "⚠️ Could not parse Behave results" + ;; + missing_log) + echo "⚠️ Behave log file missing" + ;; + *) + echo "⚠️ Unknown status: ${STATUS:-unknown}" + ;; + esac + + echo "" + echo "### Scenario Counts" + echo "| Metric | Count |" + echo "|--------|-------|" + echo "| Total Scenarios | ${TOTAL_TESTS:-0} |" + echo "| Passed Scenarios | ${PASSED_TESTS:-0} |" + echo "| Failed Scenarios | ${FAILED_TESTS:-0} |" + echo "| Skipped Scenarios | ${IGNORED_TESTS:-0} |" + echo "| Untested Scenarios | ${BEHAVE_UNTESTED_SCENARIOS:-0} |" + + echo "" + echo "### Behave Summary" + echo "| Metric | Summary |" + echo "|--------|---------|" + echo "| Features | ${BEHAVE_FEATURES_SUMMARY:-unavailable} |" + echo "| Scenarios | ${BEHAVE_SCENARIOS_SUMMARY:-unavailable} |" + echo "| Steps | ${BEHAVE_STEPS_SUMMARY:-unavailable} |" + } >> "$GITHUB_STEP_SUMMARY" || true + + - name: Upload behave logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: behave-logs-${{ matrix.test }}-${{ needs.build.outputs.build_timestamp || github.run_id }} + path: | + build-logs/ + retention-days: ${{ env.LOG_RETENTION_DAYS }} + + - name: Upload Behave Metadata + if: always() + uses: actions/upload-artifact@v4 + with: + name: behave-metadata-${{ matrix.test }} + path: | + test_results*.txt + retention-days: ${{ env.LOG_RETENTION_DAYS }} + + report: + name: Generate Apache Cloudberry Behave Report + needs: [build, prepare-behave-matrix, behave] + if: always() + runs-on: ubuntu-22.04 + steps: + - name: Generate Final Report + run: | + { + echo "# Apache Cloudberry Behave Report" + echo "## Job Status" + echo "- Build Job: ${{ needs.build.result }}" + echo "- Behave Job: ${{ needs.behave.result }}" + echo "- Completion Time: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + + if [[ "${{ needs.build.result }}" == "success" && + "${{ needs.behave.result }}" =~ ^(success|skipped)$ ]]; then + echo "✅ Pipeline completed successfully" + else + echo "⚠️ Pipeline completed with failures" + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Notify on failure + if: | + needs.build.result != 'success' || + !contains(fromJson('["success","skipped"]'), needs.behave.result) + run: | + echo "::error::Behave pipeline failed! Check job summaries and logs for details" + echo "Timestamp: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + echo "Build Result: ${{ needs.build.result }}" + echo "Behave Result: ${{ needs.behave.result }}" diff --git a/.github/workflows/build-cloudberry.yml b/.github/workflows/build-cloudberry.yml index f582f13a2a1..ef66b60d913 100644 --- a/.github/workflows/build-cloudberry.yml +++ b/.github/workflows/build-cloudberry.yml @@ -223,8 +223,6 @@ jobs: DEFAULT_ENABLE_CGROUPS=false DEFAULT_ENABLE_CORE_CHECK=true DEFAULT_PG_SETTINGS_OPTIMIZER="" - DEFAULT_TEST_KIND="installcheck" - # Define base test configurations ALL_TESTS='{ "include": [ @@ -339,69 +337,6 @@ jobs: }, {"test":"ic-cbdb-parallel", "make_configs":["src/test/regress:installcheck-cbdb-parallel"] - }, - {"test":"ic-behave-state", - "make_configs":["gpMgmt/:behave"], - "test_kind":"behave", - "behave_features":[ - "test/behave/mgmt_utils/analyzedb.feature", - "test/behave/mgmt_utils/gp_bash_functions.feature", - "test/behave/mgmt_utils/gpcheckcat.feature", - "test/behave/mgmt_utils/gpcheckperf.feature", - "test/behave/mgmt_utils/gpconfig.feature", - "test/behave/mgmt_utils/minirepro.feature", - "test/behave/mgmt_utils/gpstart.feature", - "test/behave/mgmt_utils/gpstate.feature", - "test/behave/mgmt_utils/gpstop.feature" - ], - "enable_core_check":false - }, - {"test":"ic-behave-init", - "make_configs":["gpMgmt/:behave"], - "test_kind":"behave", - "behave_features":[ - "test/behave/mgmt_utils/gpactivatestandby.feature", - "test/behave/mgmt_utils/gpinitsystem.feature", - "test/behave/mgmt_utils/gpinitstandby.feature" - ], - "enable_core_check":false - }, - {"test":"ic-behave-mirror", - "make_configs":["gpMgmt/:behave"], - "test_kind":"behave", - "behave_features":[ - "test/behave/mgmt_utils/gpaddmirrors.feature", - "test/behave/mgmt_utils/gpmovemirrors.feature", - "test/behave/mgmt_utils/gprecoverseg.feature", - "test/behave/mgmt_utils/gpreload.feature", - "test/behave/mgmt_utils/replication_slots.feature" - ], - "enable_core_check":false - }, - {"test":"ic-behave-expand", - "make_configs":["gpMgmt/:behave"], - "test_kind":"behave", - "behave_features":[ - "test/behave/mgmt_utils/gpexpand.feature" - ], - "enable_core_check":false - }, - {"test":"ic-behave-package", - "make_configs":["gpMgmt/:behave"], - "test_kind":"behave", - "behave_features":[ - "test/behave/mgmt_utils/gppkg.feature" - ], - "enable_core_check":false - }, - {"test":"ic-behave-ssh", - "make_configs":["gpMgmt/:behave"], - "test_kind":"behave", - "behave_features":[ - "test/behave/mgmt_utils/gpssh.feature", - "test/behave/mgmt_utils/gpssh_exkeys.feature" - ], - "enable_core_check":false } ] }' @@ -412,13 +347,11 @@ jobs: --argjson ec "$DEFAULT_ENABLE_CGROUPS" \ --argjson ecc "$DEFAULT_ENABLE_CORE_CHECK" \ --arg opt "$DEFAULT_PG_SETTINGS_OPTIMIZER" \ - --arg tk "$DEFAULT_TEST_KIND" \ 'def get_defaults: { num_primary_mirror_pairs: ($npm|tonumber), enable_cgroups: $ec, enable_core_check: $ecc, - test_kind: $tk, pg_settings: { optimizer: $opt } @@ -1514,31 +1447,6 @@ jobs: config="${configs[$i]}" IFS=':' read -r dir target <<< "$config" - if [ "${{ matrix.test_kind }}" == "behave" ]; then - echo "=== Executing Behave test ===" - config_log="build-logs/details/make-${{ matrix.test }}-config$i.log" - behave_targets="${{ join(matrix.behave_features, ' ') }}" - - echo "Installing OS dependencies for Behave..." - dnf install -y libffi-devel || echo "Warning: failed to install libffi-devel" - - echo "Installing Python dependencies for Behave..." - su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" - - echo "Behave feature group:" - for feature in $behave_targets; do - echo "- $feature" - done - - if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave $behave_targets" \ - 2>&1 | tee -a "$config_log"; then - echo "::warning::Test execution failed for Behave" - overall_status=1 - fi - echo "=== End configuration $((i+1)) execution ===" - continue - fi - echo "=== Executing configuration $((i+1))/${#configs[@]} ===" echo "Make command: make -C $dir $target" echo "Environment:" @@ -1664,54 +1572,15 @@ jobs: continue fi - # Parse this configuration's results - if [ "${{ matrix.test_kind }}" == "behave" ]; then - scenarios_line=$(grep -E '^[0-9]+ scenarios passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ untested$' "$config_log" | tail -n 1) - features_line=$(grep -E '^[0-9]+ features passed, [0-9]+ failed, [0-9]+ skipped$' "$config_log" | tail -n 1) - steps_line=$(grep -E '^[0-9]+ steps passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ undefined, [0-9]+ untested$' "$config_log" | tail -n 1) + MAKE_NAME="${{ matrix.test }}-config$i" \ + "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/parse-test-results.sh "$config_log" + status_code=$? - if [[ -z "$scenarios_line" ]]; then - status_code=2 - else - read -r scenarios_passed scenarios_failed scenarios_skipped scenarios_untested <<< "$(echo "$scenarios_line" | sed -E 's/^([0-9]+) scenarios passed, ([0-9]+) failed, ([0-9]+) skipped, ([0-9]+) untested$/\1 \2 \3 \4/')" - total_scenarios=$((scenarios_passed + scenarios_failed + scenarios_skipped)) - - { - if [[ "$scenarios_failed" -eq 0 ]]; then - echo "STATUS=passed" - else - echo "STATUS=failed" - fi - echo "TOTAL_TESTS=${total_scenarios}" - echo "FAILED_TESTS=${scenarios_failed}" - echo "PASSED_TESTS=${scenarios_passed}" - echo "IGNORED_TESTS=${scenarios_skipped}" - echo "BEHAVE_UNTESTED_SCENARIOS=${scenarios_untested}" - echo "BEHAVE_FEATURES_SUMMARY=\"${features_line:-unavailable}\"" - echo "BEHAVE_SCENARIOS_SUMMARY=\"${scenarios_line}\"" - echo "BEHAVE_STEPS_SUMMARY=\"${steps_line:-unavailable}\"" - echo "SUITE_NAME=${{ matrix.test }}" - echo "DIR=${dir}" - echo "TARGET=${target}" - } > test_results.txt - - if [[ "$scenarios_failed" -eq 0 ]]; then - status_code=0 - else - status_code=1 - fi - fi - else - MAKE_NAME="${{ matrix.test }}-config$i" \ - "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/parse-test-results.sh "$config_log" - status_code=$? - - { - echo "SUITE_NAME=${{ matrix.test }}" - echo "DIR=${dir}" - echo "TARGET=${target}" - } >> test_results.txt - fi + { + echo "SUITE_NAME=${{ matrix.test }}" + echo "DIR=${dir}" + echo "TARGET=${target}" + } >> test_results.txt # Process return code case $status_code in @@ -1915,17 +1784,6 @@ jobs: echo "| Failed Tests | ${FAILED_TESTS:-0} |" echo "| Ignored Tests | ${IGNORED_TESTS:-0} |" - if [[ -n "${BEHAVE_SCENARIOS_SUMMARY:-}" ]]; then - echo "" - echo "#### Behave Summary" - echo "| Metric | Summary |" - echo "|--------|---------|" - echo "| Features | ${BEHAVE_FEATURES_SUMMARY:-unavailable} |" - echo "| Scenarios | ${BEHAVE_SCENARIOS_SUMMARY} |" - echo "| Steps | ${BEHAVE_STEPS_SUMMARY:-unavailable} |" - echo "| Untested Scenarios | ${BEHAVE_UNTESTED_SCENARIOS:-0} |" - fi - # Add failed tests if any if [[ -n "${FAILED_TEST_NAMES:-}" && "${FAILED_TESTS:-0}" != "0" ]]; then echo "" From af1f1b7e7ef4b640527a113f659cc729fdc1d6ab Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 27 Mar 2026 17:05:50 +0800 Subject: [PATCH 7/8] Enable PR and push triggers for Behave workflow --- .github/workflows/behave-cloudberry.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/behave-cloudberry.yml b/.github/workflows/behave-cloudberry.yml index 71a9d0b0803..a466ff5af1c 100644 --- a/.github/workflows/behave-cloudberry.yml +++ b/.github/workflows/behave-cloudberry.yml @@ -48,7 +48,7 @@ # - **Primary Test Scope**: `gpMgmt/test/behave/mgmt_utils` # # Notes: -# - Trigger mode: manual `workflow_dispatch` only. +# - Trigger mode: push, pull_request, and manual `workflow_dispatch`. # - Behave tests are split by command to reduce cross-feature environment # pollution. # - This workflow currently focuses on single-host CI-compatible Behave tests. @@ -58,6 +58,11 @@ name: Apache Cloudberry Behave on: + push: + branches: [main, REL_2_STABLE] + pull_request: + branches: [main, REL_2_STABLE] + types: [opened, synchronize, reopened, edited] workflow_dispatch: inputs: test_selection: @@ -91,7 +96,7 @@ jobs: run: | echo "=== Behave Matrix Preparation Diagnostics ===" echo "Event type: ${{ github.event_name }}" - echo "Test selection input: '${{ github.event.inputs.test_selection }}'" + echo "Test selection input: '${{ github.event.inputs.test_selection || 'all' }}'" ALL_BEHAVE_TESTS='{ "include": [ @@ -117,7 +122,7 @@ jobs: }' VALID_TESTS=$(echo "$ALL_BEHAVE_TESTS" | jq -r '.include[].test') - IFS=',' read -ra SELECTED_TESTS <<< "${{ github.event.inputs.test_selection }}" + IFS=',' read -ra SELECTED_TESTS <<< "${{ github.event.inputs.test_selection || 'all' }}" if [[ "${SELECTED_TESTS[*]}" == "all" || -z "${SELECTED_TESTS[*]}" ]]; then mapfile -t SELECTED_TESTS <<< "$VALID_TESTS" @@ -508,7 +513,7 @@ jobs: chmod 1777 "/tmp/cloudberry-cores" sysctl -w kernel.core_pattern="/tmp/cloudberry-cores/core-%e-%s-%u-%g-%p-%t" - sudo dnf install -y libffi-devel || echo "Warning: failed to install libffi-devel" + dnf install -y libffi-devel || echo "Warning: failed to install libffi-devel" su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" echo "Running features:" From c9cc81aac906f0b4737a6814fbdb3e657998840b Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 27 Mar 2026 19:07:47 +0800 Subject: [PATCH 8/8] Fix tests --- .github/workflows/behave-cloudberry.yml | 66 ++++++++++++++----- gpAux/gpdemo/demo_cluster.sh | 6 +- gpMgmt/bin/analyzedb | 5 +- .../gppylib/test/unit/test_unit_analyzedb.py | 22 +++++++ .../test/behave/mgmt_utils/gpcheckcat.feature | 9 +-- gpMgmt/test/behave/mgmt_utils/gpssh.feature | 3 +- gpMgmt/test/behave/mgmt_utils/gpstate.feature | 4 +- .../test/behave/mgmt_utils/minirepro.feature | 1 + .../mgmt_utils/replication_slots.feature | 19 +++++- .../behave/mgmt_utils/steps/mgmt_utils.py | 1 + .../mgmt_utils/steps/minirepro_mgmt_utils.py | 8 +-- 11 files changed, 107 insertions(+), 37 deletions(-) create mode 100644 gpMgmt/bin/gppylib/test/unit/test_unit_analyzedb.py diff --git a/.github/workflows/behave-cloudberry.yml b/.github/workflows/behave-cloudberry.yml index a466ff5af1c..177db4f8791 100644 --- a/.github/workflows/behave-cloudberry.yml +++ b/.github/workflows/behave-cloudberry.yml @@ -103,21 +103,48 @@ jobs: {"test":"ic-behave-analyzedb","behave_features":["test/behave/mgmt_utils/analyzedb.feature"]}, {"test":"ic-behave-gp-bash-functions","behave_features":["test/behave/mgmt_utils/gp_bash_functions.feature"]}, {"test":"ic-behave-gpactivatestandby","behave_features":["test/behave/mgmt_utils/gpactivatestandby.feature"]}, - {"test":"ic-behave-gpaddmirrors","behave_features":["test/behave/mgmt_utils/gpaddmirrors.feature"]}, + {"test":"ic-behave-gpaddmirrors", + "behave_features":["test/behave/mgmt_utils/gpaddmirrors.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, {"test":"ic-behave-gpcheckcat","behave_features":["test/behave/mgmt_utils/gpcheckcat.feature"]}, - {"test":"ic-behave-gpcheckperf","behave_features":["test/behave/mgmt_utils/gpcheckperf.feature"]}, + {"test":"ic-behave-gpcheckperf", + "behave_features":["test/behave/mgmt_utils/gpcheckperf.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, {"test":"ic-behave-gpconfig","behave_features":["test/behave/mgmt_utils/gpconfig.feature"]}, - {"test":"ic-behave-gpinitstandby","behave_features":["test/behave/mgmt_utils/gpinitstandby.feature"]}, + {"test":"ic-behave-gpinitstandby", + "behave_features":["test/behave/mgmt_utils/gpinitstandby.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, {"test":"ic-behave-gpinitsystem","behave_features":["test/behave/mgmt_utils/gpinitsystem.feature"]}, - {"test":"ic-behave-gpmovemirrors","behave_features":["test/behave/mgmt_utils/gpmovemirrors.feature"]}, - {"test":"ic-behave-gprecoverseg","behave_features":["test/behave/mgmt_utils/gprecoverseg.feature"]}, + {"test":"ic-behave-gpmovemirrors", + "behave_features":["test/behave/mgmt_utils/gpmovemirrors.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, + {"test":"ic-behave-gprecoverseg", + "behave_features":["test/behave/mgmt_utils/gprecoverseg.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, {"test":"ic-behave-gpreload","behave_features":["test/behave/mgmt_utils/gpreload.feature"]}, - {"test":"ic-behave-gpstart","behave_features":["test/behave/mgmt_utils/gpstart.feature"]}, - {"test":"ic-behave-gpstate","behave_features":["test/behave/mgmt_utils/gpstate.feature"]}, + {"test":"ic-behave-gpstart", + "behave_features":["test/behave/mgmt_utils/gpstart.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, + {"test":"ic-behave-gpstate", + "behave_features":["test/behave/mgmt_utils/gpstate.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, {"test":"ic-behave-gpstop","behave_features":["test/behave/mgmt_utils/gpstop.feature"]}, - {"test":"ic-behave-gpssh","behave_features":["test/behave/mgmt_utils/gpssh.feature"]}, + {"test":"ic-behave-gpssh", + "behave_features":["test/behave/mgmt_utils/gpssh.feature"], + "behave_args":"--tags ~@requires_netem" + }, {"test":"ic-behave-minirepro","behave_features":["test/behave/mgmt_utils/minirepro.feature"]}, - {"test":"ic-behave-replication-slots","behave_features":["test/behave/mgmt_utils/replication_slots.feature"]} + {"test":"ic-behave-replication-slots", + "behave_features":["test/behave/mgmt_utils/replication_slots.feature"], + "behave_args":"--tags ~@extended" + } ] }' @@ -508,6 +535,7 @@ jobs: mkdir -p build-logs/details config_log="build-logs/details/make-${{ matrix.test }}-config0.log" behave_targets="${{ join(matrix.behave_features, ' ') }}" + behave_args="${{ matrix.behave_args || '' }}" mkdir -p "/tmp/cloudberry-cores" chmod 1777 "/tmp/cloudberry-cores" @@ -520,8 +548,11 @@ jobs: for feature in $behave_targets; do echo "- $feature" done + if [[ -n "$behave_args" ]]; then + echo "Behave args: $behave_args" + fi - if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave $behave_targets" \ + if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave $behave_args $behave_targets" \ 2>&1 | tee -a "$config_log"; then echo "::warning::Behave execution reported failures" exit 1 @@ -534,9 +565,10 @@ jobs: set -o pipefail config_log="build-logs/details/make-${{ matrix.test }}-config0.log" + behave_cmd="behave ${{ matrix.behave_args || '' }} ${{ join(matrix.behave_features, ' ') }}" if [ ! -f "$config_log" ]; then { - echo "MAKE_COMMAND=\"behave ${{ join(matrix.behave_features, ' ') }}\"" + echo "MAKE_COMMAND=\"${behave_cmd}\"" echo "STATUS=missing_log" echo "TOTAL_TESTS=0" echo "FAILED_TESTS=0" @@ -546,13 +578,13 @@ jobs: exit 1 fi - features_line=$(grep -E '^[0-9]+ features passed, [0-9]+ failed, [0-9]+ skipped$' "$config_log" | tail -n 1) - scenarios_line=$(grep -E '^[0-9]+ scenarios passed, [0-9]+ failed, [0-9]+ skipped(, [0-9]+ untested)?$' "$config_log" | tail -n 1) - steps_line=$(grep -E '^[0-9]+ steps passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ undefined(, [0-9]+ untested)?$' "$config_log" | tail -n 1) + features_line=$(grep -E '^[0-9]+ feature(s)? passed, [0-9]+ failed, [0-9]+ skipped$' "$config_log" | tail -n 1) + scenarios_line=$(grep -E '^[0-9]+ scenario(s)? passed, [0-9]+ failed, [0-9]+ skipped(, [0-9]+ untested)?$' "$config_log" | tail -n 1) + steps_line=$(grep -E '^[0-9]+ step(s)? passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ undefined(, [0-9]+ untested)?$' "$config_log" | tail -n 1) if [[ -z "$scenarios_line" ]]; then { - echo "MAKE_COMMAND=\"behave ${{ join(matrix.behave_features, ' ') }}\"" + echo "MAKE_COMMAND=\"${behave_cmd}\"" echo "STATUS=parse_error" echo "TOTAL_TESTS=0" echo "FAILED_TESTS=0" @@ -562,13 +594,13 @@ jobs: exit 1 fi - scenario_counts=$(echo "$scenarios_line" | sed -E 's/^([0-9]+) scenarios passed, ([0-9]+) failed, ([0-9]+) skipped(, ([0-9]+) untested)?$/\1 \2 \3 \5/') + scenario_counts=$(echo "$scenarios_line" | sed -E 's/^([0-9]+) scenario(s)? passed, ([0-9]+) failed, ([0-9]+) skipped(, ([0-9]+) untested)?$/\1 \3 \4 \6/') read -r scenarios_passed scenarios_failed scenarios_skipped scenarios_untested <<< "$scenario_counts" scenarios_untested=${scenarios_untested:-0} total_scenarios=$((scenarios_passed + scenarios_failed + scenarios_skipped)) { - echo "MAKE_COMMAND=\"behave ${{ join(matrix.behave_features, ' ') }}\"" + echo "MAKE_COMMAND=\"${behave_cmd}\"" if [[ "$scenarios_failed" -eq 0 ]]; then echo "STATUS=passed" else diff --git a/gpAux/gpdemo/demo_cluster.sh b/gpAux/gpdemo/demo_cluster.sh index 225bb76a5ee..7397894d359 100755 --- a/gpAux/gpdemo/demo_cluster.sh +++ b/gpAux/gpdemo/demo_cluster.sh @@ -314,8 +314,10 @@ cat >> $CLUSTER_CONFIG <<-EOF COORDINATOR_PORT=${COORDINATOR_DEMO_PORT} - # Shell to use to execute commands on all hosts - TRUSTED_SHELL="$(dirname "$0")/lalshell" + # Shell to use to execute commands on all hosts. Use an absolute path here + # because this file is later sourced by gpinitsystem, where \$0 is no longer + # demo_cluster.sh. + TRUSTED_SHELL=$(pwd)/lalshell ENCODING=UNICODE EOF diff --git a/gpMgmt/bin/analyzedb b/gpMgmt/bin/analyzedb index 48d8e16872c..082d6af8377 100755 --- a/gpMgmt/bin/analyzedb +++ b/gpMgmt/bin/analyzedb @@ -951,7 +951,10 @@ class AnalyzeDb(Operation): # Create a Command object that executes a query using psql. def create_psql_command(dbname, query): psql_cmd = """psql %s -c %s""" % (pipes.quote(dbname), pipes.quote(query)) - return Command(query, psql_cmd) + # Keep the command text intact for execution, but make the display name + # ASCII-safe so logger/output paths do not choke on UTF-8 identifiers. + safe_query_display = query.encode('ascii', 'backslashreplace').decode('ascii') + return Command(safe_query_display, psql_cmd) def run_sql(conn, query): diff --git a/gpMgmt/bin/gppylib/test/unit/test_unit_analyzedb.py b/gpMgmt/bin/gppylib/test/unit/test_unit_analyzedb.py new file mode 100644 index 00000000000..be3b33efd66 --- /dev/null +++ b/gpMgmt/bin/gppylib/test/unit/test_unit_analyzedb.py @@ -0,0 +1,22 @@ +import imp +import os + +from gppylib.test.unit.gp_unittest import GpTestCase, run_tests + + +class AnalyzeDbTestCase(GpTestCase): + def setUp(self): + analyzedb_file = os.path.abspath(os.path.dirname(__file__) + "/../../../analyzedb") + self.subject = imp.load_source('analyzedb', analyzedb_file) + + def test_create_psql_command_keeps_utf8_sql_but_uses_ascii_safe_display_name(self): + query = 'analyze "public"."spiegelungssätze"' + + cmd = self.subject.create_psql_command('special_encoding_db', query) + + self.assertEqual(cmd.name, 'analyze "public"."spiegelungss\\xe4tze"') + self.assertIn('spiegelungssätze', cmd.cmdStr) + + +if __name__ == '__main__': + run_tests() diff --git a/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature b/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature index d9b91838909..1c5dcc267dc 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature @@ -317,8 +317,7 @@ Feature: gpcheckcat tests And the user runs "psql extra_pk_db -c 'CREATE SCHEMA my_pk_schema' " And the user runs "psql extra_pk_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/add_operator.sql " Then psql should return a return code of 0 - And the user runs "psql extra_pk_db -c "set allow_system_table_mods=true;DELETE FROM pg_catalog.pg_operator where oprname='!#'" " - Then psql should return a return code of 0 + Then The user runs sql "set allow_system_table_mods=true;DELETE FROM pg_catalog.pg_operator where oprname='!#'" in "extra_pk_db" on first primary segment When the user runs "gpcheckcat -R missing_extraneous extra_pk_db" Then gpcheckcat should return a return code of 3 And the path "gpcheckcat.repair.*" is found in cwd "0" times @@ -728,13 +727,10 @@ Feature: gpcheckcat tests And the user runs "dropdb all_good" - Scenario: validate session GUC passed with -x is set + Scenario: gpcheckcat accepts session GUC passed with -x in single node mode Given the database is not running And the user runs "gpstart -ma" And "gpstart -ma" should return a return code of 0 - Then the user runs "gpcheckcat -R foreign_key" - Then gpcheckcat should return a return code of 1 - And gpcheckcat should print ".* System was started in single node mode - only utility mode connections are allowed" to stdout Then the user runs "gpcheckcat -x gp_role=utility -R foreign_key" Then gpcheckcat should return a return code of 0 And the user runs "gpstop -ma" @@ -742,4 +738,3 @@ Feature: gpcheckcat tests And the user runs "gpstart -a" - diff --git a/gpMgmt/test/behave/mgmt_utils/gpssh.feature b/gpMgmt/test/behave/mgmt_utils/gpssh.feature index 07d7736db56..08aa48b3ef5 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpssh.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpssh.feature @@ -24,10 +24,11 @@ Feature: gpssh behave tests And gpssh should print "unable to login to localhost" to stdout And gpssh should print "could not synchronize with original prompt" to stdout + @requires_netem Scenario: gpssh succeeds when network has latency When the user runs command "sudo tc qdisc add dev lo root netem delay 4000ms" Then sudo should return a return code of 0 When the user runs "gpssh -h localhost echo 'hello I am testing'" Then gpssh should return a return code of 0 And gpssh should print "hello I am testing" to stdout - # We depend on environment.py#after_scenario() to delete the artificial latency \ No newline at end of file + # We depend on environment.py#after_scenario() to delete the artificial latency diff --git a/gpMgmt/test/behave/mgmt_utils/gpstate.feature b/gpMgmt/test/behave/mgmt_utils/gpstate.feature index e03c7f7bd8e..49fc475c0e2 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpstate.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpstate.feature @@ -251,7 +251,7 @@ Feature: gpstate tests Scenario: gpstate -m logs mirror details Given a standard local demo cluster is running When the user runs "gpstate -m" - Then gpstate should print "Current GPDB mirror list and status" to stdout + Then gpstate should print "Current CBDB mirror list and status" to stdout And gpstate output looks like | Mirror | Datadir | Port | Status | Data Status | | \S+ | .*/dbfast_mirror1/demoDataDir0 | [0-9]+ | Passive | Synchronized | @@ -263,7 +263,7 @@ Feature: gpstate tests And user stops all primary processes And user can start transactions When the user runs "gpstate -m" - Then gpstate should print "Current GPDB mirror list and status" to stdout + Then gpstate should print "Current CBDB mirror list and status" to stdout And gpstate output looks like | Mirror | Datadir | Port | Status | Data Status | | \S+ | .*/dbfast_mirror1/demoDataDir0 | [0-9]+ | Acting as Primary | Not In Sync | diff --git a/gpMgmt/test/behave/mgmt_utils/minirepro.feature b/gpMgmt/test/behave/mgmt_utils/minirepro.feature index 15e9c666a51..14a7f5fea6a 100644 --- a/gpMgmt/test/behave/mgmt_utils/minirepro.feature +++ b/gpMgmt/test/behave/mgmt_utils/minirepro.feature @@ -28,6 +28,7 @@ Feature: Dump minimum database objects that is related to the query @minirepro_UI Scenario: Database does not exist Given database "nonedb000" does not exist + And the file "/home/gpadmin/test/in.sql" exists and contains "select 1;" When the user runs "minirepro nonedb000 -q ~/test/in.sql -f ~/out.sql" Then minirepro error should contain database "nonedb000" does not exist diff --git a/gpMgmt/test/behave/mgmt_utils/replication_slots.feature b/gpMgmt/test/behave/mgmt_utils/replication_slots.feature index 121c7abf783..cbbb3c167cc 100644 --- a/gpMgmt/test/behave/mgmt_utils/replication_slots.feature +++ b/gpMgmt/test/behave/mgmt_utils/replication_slots.feature @@ -1,11 +1,15 @@ @replication_slots Feature: Replication Slots - Scenario: Lifecycle of cluster's replication slots + Scenario: Replication slots are created for a new mirrored cluster Given I have a machine with no cluster When I create a cluster Then the primaries and mirrors should be replicating using replication slots + Scenario: Replication slots remain correct after failover and rebalance + Given I have a machine with no cluster + And I create a cluster + Given a preferred primary has failed When the user runs "gprecoverseg -a" And gprecoverseg should return a return code of 0 @@ -19,18 +23,27 @@ Feature: Replication Slots And the segments are synchronized And the primaries and mirrors should be replicating using replication slots + @extended + Scenario: Replication slots remain correct after full recovery + Given I have a machine with no cluster + And I create a cluster + When a mirror has crashed And the user runs "gprecoverseg -aFv" And gprecoverseg should return a return code of 0 And the segments are synchronized Then the primaries and mirrors should be replicating using replication slots + @extended + Scenario: Replication slots remain correct after expansion + Given I have a machine with no cluster + And I create a cluster + When I add a segment to the cluster And the segments are synchronized Then the primaries and mirrors should be replicating using replication slots - Scenario: A adding mirrors to a cluster after the primaries have been initialized + Scenario: Replication slots are created when mirrors are added later Given I cluster with no mirrors When I add mirrors to the cluster Then the primaries and mirrors should be replicating using replication slots - diff --git a/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py b/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py index 45e888dbcd4..9f9ff517188 100644 --- a/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py +++ b/gpMgmt/test/behave/mgmt_utils/steps/mgmt_utils.py @@ -23,6 +23,7 @@ from gppylib.gparray import GpArray, ROLE_PRIMARY, ROLE_MIRROR from gppylib.commands.gp import SegmentStart, GpStandbyStart, CoordinatorStop from gppylib.commands import gp +from gppylib.commands import unix from gppylib.commands.pg import PgBaseBackup from gppylib.operations.startSegments import MIRROR_MODE_MIRRORLESS from gppylib.operations.buildMirrorSegments import get_recovery_progress_pattern diff --git a/gpMgmt/test/behave/mgmt_utils/steps/minirepro_mgmt_utils.py b/gpMgmt/test/behave/mgmt_utils/steps/minirepro_mgmt_utils.py index 2a8599f25fc..9cde76affc8 100644 --- a/gpMgmt/test/behave/mgmt_utils/steps/minirepro_mgmt_utils.py +++ b/gpMgmt/test/behave/mgmt_utils/steps/minirepro_mgmt_utils.py @@ -1,4 +1,4 @@ -import os, mmap +import os import re from test.behave_utils.utils import drop_database_if_exists, drop_table_if_exists @@ -41,7 +41,7 @@ def impl(context, output_file): @then('the output file "{output_file}" should contain "{str_before}" before "{str_after}"') def impl(context, output_file, str_before, str_after): with open(output_file, 'r') as output_f: - s = mmap.mmap(output_f.fileno(), 0, access=mmap.ACCESS_READ) + s = output_f.read() pos_before = s.find(str_before) pos_after = s.find(str_after) if pos_before == -1: @@ -54,14 +54,14 @@ def impl(context, output_file, str_before, str_after): @then('the output file "{output_file}" should contain "{search_str}"') def impl(context, output_file, search_str): with open(output_file, 'r') as output_f: - s = mmap.mmap(output_f.fileno(), 0, access=mmap.ACCESS_READ) + s = output_f.read() if s.find(search_str) == -1: raise Exception('%s not found.' % search_str) @then('the output file "{output_file}" should not contain "{search_str}"') def impl(context, output_file, search_str): with open(output_file, 'r') as output_f: - s = mmap.mmap(output_f.fileno(), 0, access=mmap.ACCESS_READ) + s = output_f.read() if s.find(search_str) != -1: raise Exception('%s should not exist.' % search_str)