diff --git a/.github/workflows/rustfs-heal-test.yml b/.github/workflows/rustfs-heal-test.yml index 00486eb96..995cc9b23 100644 --- a/.github/workflows/rustfs-heal-test.yml +++ b/.github/workflows/rustfs-heal-test.yml @@ -152,6 +152,60 @@ jobs: else PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi + STEPS_TABLE="/tmp/rustfs-heal-steps.md" + python3 - "${LOG_FILE}" "${STEPS_TABLE}" <<'PY' + import re + import sys + + log_file, out_file = sys.argv[1], sys.argv[2] + ansi = re.compile(r'\x1b\[[0-9;]*m') + step_re = re.compile(r'^\[HEAL-STEP\]\s+(\d+)\s+(.+?)\s+(PASS|FAIL|SKIP)\s*$') + ver_re = re.compile(r'^\[HEAL-VERSION\]\s+(\S+)(?:\s+\(node\s+(\S+)\))?\s*$') + result_re = re.compile(r'^\[HEAL-RESULT\]\s+(PASS|FAIL)\s+(.*)$') + + steps = {} + order = [] + version = None + version_node = None + verdict = None + verdict_detail = '' + try: + with open(log_file, 'r', encoding='utf-8', errors='replace') as fh: + for raw in fh: + line = ansi.sub('', raw).strip() + m = step_re.match(line) + if m: + n, desc, status = m.group(1), m.group(2), m.group(3) + if n not in steps: + order.append(n) + steps[n] = (desc, status) # later lines win (fail after pass) + continue + m = ver_re.match(line) + if m: + version, version_node = m.group(1), m.group(2) + continue + m = result_re.match(line) + if m: + verdict, verdict_detail = m.group(1), m.group(2) + except FileNotFoundError: + pass + + with open(out_file, 'w', encoding='utf-8') as out: + out.write('## Step Results\n\n') + if version: + node_note = f' (captured via `rustfs --version` on {version_node})' if version_node else '' + out.write(f'- Version under test: **{version}**{node_note}\n') + if verdict: + out.write(f'- Overall result: **{verdict}** — {verdict_detail}\n') + out.write('\n') + out.write('| Step | Description | Result |\n') + out.write('| --- | --- | --- |\n') + for n in sorted(order, key=int): + desc, status = steps[n] + out.write(f'| {n} | {desc} | {status} |\n') + if not order: + out.write('| - | - | NOT RUN (no step result lines found) |\n') + PY { echo "# RustFS heal test report" echo "" @@ -160,6 +214,8 @@ jobs: echo "- Package: ${PACKAGE_SOURCE}" echo "- Test Step Outcome: ${{ steps.test.outcome }}" echo "" + cat "${STEPS_TABLE}" || true + echo "" echo "## Log tail" echo '```text' tail -n 200 "${LOG_FILE}" || true diff --git a/.github/workflows/rustfs-pool-expand-test.yml b/.github/workflows/rustfs-pool-expand-test.yml index d7c0d6abc..d7002c446 100644 --- a/.github/workflows/rustfs-pool-expand-test.yml +++ b/.github/workflows/rustfs-pool-expand-test.yml @@ -380,6 +380,60 @@ jobs: else PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi + STEPS_TABLE="${POOL_ARTIFACT_DIR}/pool-steps.md" + python3 - "${LOG_FILE}" "${STEPS_TABLE}" <<'PY' + import re + import sys + + log_file, out_file = sys.argv[1], sys.argv[2] + ansi = re.compile(r'\x1b\[[0-9;]*m') + step_re = re.compile(r'^\[POOL-STEP\]\s+(\d+)\s+(.+?)\s+(PASS|FAIL|SKIP)\s*$') + ver_re = re.compile(r'^\[POOL-VERSION\]\s+(\S+)(?:\s+\(node\s+(\S+)\))?\s*$') + result_re = re.compile(r'^\[POOL-RESULT\]\s+(PASS|FAIL)\s+(.*)$') + + steps = {} + order = [] + version = None + version_node = None + verdict = None + verdict_detail = '' + try: + with open(log_file, 'r', encoding='utf-8', errors='replace') as fh: + for raw in fh: + line = ansi.sub('', raw).strip() + m = step_re.match(line) + if m: + n, desc, status = m.group(1), m.group(2), m.group(3) + if n not in steps: + order.append(n) + steps[n] = (desc, status) # later lines win (fail after pass) + continue + m = ver_re.match(line) + if m: + version, version_node = m.group(1), m.group(2) + continue + m = result_re.match(line) + if m: + verdict, verdict_detail = m.group(1), m.group(2) + except FileNotFoundError: + pass + + with open(out_file, 'w', encoding='utf-8') as out: + out.write('## Step Results\n\n') + if version: + node_note = f' (captured via `rustfs --version` on {version_node})' if version_node else '' + out.write(f'- Version under test: **{version}**{node_note}\n') + if verdict: + out.write(f'- Overall result: **{verdict}** — {verdict_detail}\n') + out.write('\n') + out.write('| Step | Description | Result |\n') + out.write('| --- | --- | --- |\n') + for n in sorted(order, key=int): + desc, status = steps[n] + out.write(f'| {n} | {desc} | {status} |\n') + if not order: + out.write('| - | - | NOT RUN (no step result lines found) |\n') + PY { echo "# RustFS pool expansion test report" echo "" @@ -389,6 +443,8 @@ jobs: echo "- Warp concurrent: ${{ inputs.warp_concurrent || '32' }}" echo "- Test Step Outcome: ${{ steps.pool_test.outcome }}" echo "" + cat "${STEPS_TABLE}" || true + echo "" echo "## Log tail" echo '```text' tail -n 200 "${LOG_FILE}" || true diff --git a/.github/workflows/rustfs-upgrade-test.yml b/.github/workflows/rustfs-upgrade-test.yml index 0279a9fcb..d822b4990 100644 --- a/.github/workflows/rustfs-upgrade-test.yml +++ b/.github/workflows/rustfs-upgrade-test.yml @@ -18,9 +18,9 @@ on: workflow_dispatch: inputs: from_version: - description: 'OLD RustFS release tag (e.g. 1.0.0-rc.4-preview.1)' + description: 'OLD RustFS release tag (must ship a .deb asset, e.g. 1.0.0-rc.3)' required: false - default: '1.0.0-rc.4-preview.1' + default: '1.0.0-rc.3' from_url: description: 'OLD .deb URL. Overrides from_version.' required: false @@ -203,54 +203,75 @@ jobs: TO_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi CASE_TABLE="/tmp/rustfs-upgrade-cases.md" - python3 - "${LOG_FILE}" "${CASE_TABLE}" <<'PY' + MATRIX_TABLE="/tmp/rustfs-upgrade-matrix.md" + python3 - "${LOG_FILE}" "${CASE_TABLE}" "${MATRIX_TABLE}" <<'PY' import re import sys - log_file, out_file = sys.argv[1], sys.argv[2] + log_file, out_file, matrix_file = sys.argv[1], sys.argv[2], sys.argv[3] ansi = re.compile(r'\x1b\[[0-9;]*m') start_re = re.compile(r'^---\s+([A-Z]+-[0-9]+)\s+(.+?)\s+---$') done_re = re.compile(r'^\[(PASS|FAIL|UNSUPPORTED)\]\s+([A-Z]+-[0-9]+)\b') + topo_re = re.compile( + r'^\[UPG-TOPO\]\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+PASS=(\d+)\s+FAIL=(\d+)\s*$') rows = [] index = {} + topo_rows = [] try: - with open(log_file, 'r', encoding='utf-8', errors='replace') as fh: - for raw in fh: - line = ansi.sub('', raw).strip() - m = start_re.match(line) - if m: - case_id, name = m.group(1), m.group(2) - if case_id not in index: - index[case_id] = len(rows) - rows.append([case_id, name, 'RUNNING']) - continue - m = done_re.match(line) - if m: - status, case_id = m.group(1), m.group(2) - if case_id in index: - rows[index[case_id]][2] = status - else: - rows.append([case_id, case_id, status]) - index[case_id] = len(rows) - 1 + with open(log_file, 'r', encoding='utf-8', errors='replace') as fh: + for raw in fh: + line = ansi.sub('', raw).strip() + m = topo_re.match(line) + if m: + topo_rows.append(m.groups()) + continue + m = start_re.match(line) + if m: + case_id, name = m.group(1), m.group(2) + if case_id not in index: + index[case_id] = len(rows) + rows.append([case_id, name, 'RUNNING']) + continue + m = done_re.match(line) + if m: + status, case_id = m.group(1), m.group(2) + if case_id in index: + rows[index[case_id]][2] = status + else: + rows.append([case_id, case_id, status]) + index[case_id] = len(rows) - 1 except FileNotFoundError: - rows = [] + rows = [] counts = {'PASS': 0, 'FAIL': 0, 'UNSUPPORTED': 0, 'RUNNING': 0} for _, _, status in rows: - counts[status] = counts.get(status, 0) + 1 + counts[status] = counts.get(status, 0) + 1 with open(out_file, 'w', encoding='utf-8') as out: - out.write('## Case Summary\n\n') - out.write(f"- Total: {len(rows)}\\n") - out.write(f"- PASS: {counts.get('PASS', 0)}\\n") - out.write(f"- FAIL: {counts.get('FAIL', 0)}\\n") - out.write(f"- UNSUPPORTED: {counts.get('UNSUPPORTED', 0)}\\n") - out.write('\\n') - out.write('| Case | Name | Status |\\n') - out.write('| --- | --- | --- |\\n') - for case_id, name, status in rows: - out.write(f'| {case_id} | {name} | {status} |\\n') + out.write('## Case Summary\n\n') + out.write(f"- Total: {len(rows)}\\n") + out.write(f"- PASS: {counts.get('PASS', 0)}\\n") + out.write(f"- FAIL: {counts.get('FAIL', 0)}\\n") + out.write(f"- UNSUPPORTED: {counts.get('UNSUPPORTED', 0)}\\n") + out.write('\\n') + out.write('| Case | Name | Status |\\n') + out.write('| --- | --- | --- |\\n') + for case_id, name, status in rows: + out.write(f'| {case_id} | {name} | {status} |\\n') + + # Upgrade matrix: one row per topology/backend with the versions + # captured on the nodes (rustfs --version) and the aggregated + # result. The dashboard renders this table directly. + with open(matrix_file, 'w', encoding='utf-8') as out: + out.write('## Upgrade Matrix\n\n') + out.write('| Topology | KMS Backend | From Version | To Version | Result |\n') + out.write('| --- | --- | --- | --- | --- |\n') + for topo, backend, old_v, new_v, npass, nfail in topo_rows: + result = 'PASS' if nfail == '0' else 'FAIL' + out.write(f'| {topo} | {backend} | {old_v} | {new_v} | {result} (PASS={npass} FAIL={nfail}) |\n') + if not topo_rows: + out.write('| - | - | - | - | NOT RUN (suite failed before upgrade) |\n') PY { echo "# RustFS upgrade compatibility report" @@ -261,6 +282,8 @@ jobs: echo "- To: ${TO_SOURCE}" echo "- Test Step Outcome: ${{ steps.test.outcome }}" echo "" + cat "${MATRIX_TABLE}" || true + echo "" cat "${CASE_TABLE}" || true echo "" echo "## Log tail"