All of lore.kernel.org
 help / color / mirror / Atom feed
From: Luis Chamberlain <mcgrof@kernel.org>
To: Chuck Lever <cel@kernel.org>, Daniel Gomez <da.gomez@kruces.com>,
	kdevops@lists.linux.dev
Cc: Luis Chamberlain <mcgrof@kernel.org>
Subject: [PATCH v2 9/9] bootlinux: add support for A/B kernel testing
Date: Tue, 29 Jul 2025 23:01:45 -0700	[thread overview]
Message-ID: <20250730060147.182140-10-mcgrof@kernel.org> (raw)
In-Reply-To: <20250730060147.182140-1-mcgrof@kernel.org>

Right now we use the same kernel for all target nodes. We want to
compare and contrast different kenrels for different features. We
add support for A/B testing by leveraging the baseline and dev groups
provided to us by KDEVOPS_BASELINE_AND_DEV.

This extends the bootlinux playbook by enabling us to allow a different
kernel tree / ref to be used for the dev group. This just becomes a
configuration thing. The targets are intuitive:

  make linux                 # Handles A/B compilation transparently
  make linux-baseline        # Build and install baseline kernel only
  make linux-dev             # Build and install development kernel only

We also add a simple check to verify all different build types end
up respecting different kernels if we so choose:

  make check-linux-ab

This does not launch targets it just verifies we don't regress in the
future with the different ref tags.

Generated-by: Claude AI
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
---
 .github/workflows/linux-ab-testing.yml       | 217 +++++++++++++++++++
 .github/workflows/linux-ab.yml               |  47 ++++
 Makefile                                     |   1 +
 PROMPTS.md                                   |  52 +++++
 defconfigs/linux-ab-testing                  |  14 ++
 defconfigs/linux-ab-testing-9p               |  15 ++
 defconfigs/linux-ab-testing-builder          |  15 ++
 defconfigs/linux-ab-testing-target           |  15 ++
 docs/kdevops-make-linux.md                   | 158 ++++++++++++++
 playbooks/roles/bootlinux/defaults/main.yml  |  14 ++
 playbooks/roles/bootlinux/tasks/build/9p.yml |  20 +-
 playbooks/roles/bootlinux/tasks/main.yml     | 112 ++++++++++
 scripts/infer_last_stable_kernel.sh          |  35 +++
 scripts/linux-ab-testing.Makefile            |  51 +++++
 scripts/test-linux-ab-config.py              | 182 ++++++++++++++++
 scripts/test-linux-ab.sh                     | 213 ++++++++++++++++++
 workflows/linux/Kconfig                      | 102 ++++++++-
 workflows/linux/Makefile                     |  39 ++++
 18 files changed, 1291 insertions(+), 11 deletions(-)
 create mode 100644 .github/workflows/linux-ab-testing.yml
 create mode 100644 .github/workflows/linux-ab.yml
 create mode 100644 defconfigs/linux-ab-testing
 create mode 100644 defconfigs/linux-ab-testing-9p
 create mode 100644 defconfigs/linux-ab-testing-builder
 create mode 100644 defconfigs/linux-ab-testing-target
 create mode 100755 scripts/infer_last_stable_kernel.sh
 create mode 100644 scripts/linux-ab-testing.Makefile
 create mode 100755 scripts/test-linux-ab-config.py
 create mode 100755 scripts/test-linux-ab.sh

diff --git a/.github/workflows/linux-ab-testing.yml b/.github/workflows/linux-ab-testing.yml
new file mode 100644
index 00000000..aa52abbb
--- /dev/null
+++ b/.github/workflows/linux-ab-testing.yml
@@ -0,0 +1,217 @@
+name: Linux A/B Testing Verification
+
+on:
+  push:
+    branches:
+      - '**'
+  pull_request:
+    branches:
+      - '**'
+  workflow_dispatch:  # Allow manual triggering
+
+jobs:
+  linux-ab-testing-verification:
+    name: Verify Linux A/B Testing Variables
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        distro_container:
+          - debian:testing
+          - fedora:latest
+        build_method:
+          - target
+          - 9p
+          - builder
+
+    container: ${{ matrix.distro_container }}
+    steps:
+      - name: Document test environment
+        run: |
+          echo "Running Linux A/B testing verification on ${{ matrix.distro_container }}"
+          echo "Build method: ${{ matrix.build_method }}"
+          uname -a
+
+      - name: Install dependencies
+        run: |
+          if [ "${{ matrix.distro_container }}" = "debian:testing" ]; then
+            echo "Installing packages for Debian"
+            apt-get update
+            apt-get install -y ansible-core make gcc ncurses-dev bison flex git python3
+          elif [ "${{ matrix.distro_container }}" = "fedora:latest" ]; then
+            echo "Installing packages for Fedora"
+            dnf install -y ansible make gcc ncurses-devel bison flex git python3
+          else
+            echo "Unknown distribution: ${{ matrix.distro_container }}"
+            exit 1
+          fi
+
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Configure git for kdevops
+        run: |
+          git config --global --add safe.directory '*'
+          git config --global user.name "kdevops-ci"
+          git config --global user.email "kdevops@lists.linux.dev"
+
+      - name: Apply A/B testing defconfig
+        run: |
+          echo "Applying linux-ab-testing-${{ matrix.build_method }} defconfig"
+          make defconfig-linux-ab-testing-${{ matrix.build_method }}
+
+          # Verify configuration was applied correctly
+          echo "=== Verifying A/B testing configuration ==="
+          grep -E "CONFIG_KDEVOPS_BASELINE_AND_DEV=y" .config || exit 1
+          grep -E "CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y" .config || exit 1
+
+          # Check build method specific configs
+          case "${{ matrix.build_method }}" in
+            target)
+              grep -E "CONFIG_BOOTLINUX_TARGET=y" .config || exit 1
+              ;;
+            9p)
+              grep -E "CONFIG_BOOTLINUX_9P=y" .config || exit 1
+              ;;
+            builder)
+              grep -E "CONFIG_BOOTLINUX_BUILDER=y" .config || exit 1
+              ;;
+          esac
+
+      - name: Run make to generate extra_vars.yaml
+        run: |
+          make
+
+      - name: Extract and verify kernel references
+        run: |
+          echo "=== Extracting kernel references from configuration ==="
+
+          # Get the baseline ref (should be master or main)
+          BASELINE_REF=$(grep "^bootlinux_tree_ref:" extra_vars.yaml | awk '{print $2}')
+          echo "Baseline ref: $BASELINE_REF"
+
+          # Get the dev ref using the inference script
+          DEV_REF=$(grep "^bootlinux_dev_tree_ref:" extra_vars.yaml | awk '{print $2}')
+          echo "Dev ref from config: $DEV_REF"
+
+          # Since we're in a container without /mirror/linux.git, the script will fallback
+          # For CI, we'll simulate getting the latest stable tag
+          if [ -f scripts/infer_last_stable_kernel.sh ]; then
+            INFERRED_STABLE=$(./scripts/infer_last_stable_kernel.sh 2>/dev/null || echo "v6.12")
+            echo "Inferred stable version: $INFERRED_STABLE"
+          fi
+
+          # Verify refs are different
+          if [ "$BASELINE_REF" = "$DEV_REF" ]; then
+            echo "ERROR: Baseline and dev refs should be different for A/B testing"
+            exit 1
+          fi
+
+          # Store refs for later verification
+          echo "BASELINE_REF=$BASELINE_REF" >> $GITHUB_ENV
+          echo "DEV_REF=$DEV_REF" >> $GITHUB_ENV
+
+      - name: Test debug functionality with Ansible
+        run: |
+          echo "=== Testing debug output with DEBUG_REF=1 ==="
+
+          # Create a minimal hosts file for container testing
+          cat > hosts << EOF
+          [all]
+          localhost ansible_connection=local
+
+          [baseline]
+          localhost ansible_connection=local
+
+          [dev]
+          EOF
+
+          # Run the bootlinux playbook with debug enabled and capture output
+          export DEBUG_REF=1
+          ansible-playbook -i hosts playbooks/bootlinux.yml --tags vars,debug -v > debug_output.txt 2>&1 || true
+
+          # Verify debug output based on build method
+          case "${{ matrix.build_method }}" in
+            9p)
+              echo "=== Verifying 9P debug output (localhost context) ==="
+              if grep -q "active_linux_ref" debug_output.txt; then
+                echo "✓ Found active_linux_ref in 9P debug output"
+              else
+                echo "✗ Missing active_linux_ref in 9P debug output"
+                cat debug_output.txt
+                exit 1
+              fi
+              ;;
+            target|builder)
+              echo "=== Verifying non-9P debug output (per-node context) ==="
+              if grep -q "target_linux_ref" debug_output.txt; then
+                echo "✓ Found target_linux_ref in non-9P debug output"
+              else
+                echo "✗ Missing target_linux_ref in non-9P debug output"
+                cat debug_output.txt
+                exit 1
+              fi
+              ;;
+          esac
+
+      - name: Verify A/B testing Makefile rules
+        run: |
+          echo "=== Verifying A/B testing Makefile structure ==="
+
+          # Check that linux target depends on linux-baseline and linux-dev
+          if grep -A5 "^linux:" workflows/linux/Makefile | grep -q "linux-baseline linux-dev"; then
+            echo "✓ Makefile has correct A/B testing dependencies"
+          else
+            echo "✗ Makefile missing A/B testing dependencies"
+            exit 1
+          fi
+
+          # Verify linux-baseline and linux-dev targets exist
+          if grep -q "^linux-baseline:" workflows/linux/Makefile && \
+             grep -q "^linux-dev:" workflows/linux/Makefile; then
+            echo "✓ Both linux-baseline and linux-dev targets exist"
+          else
+            echo "✗ Missing linux-baseline or linux-dev targets"
+            exit 1
+          fi
+
+      - name: Test variable resolution patterns
+        run: |
+          echo "=== Testing variable resolution patterns ==="
+
+          # Create test playbook to verify variable resolution
+          cat > test_vars.yml << 'EOF'
+          ---
+          - hosts: localhost
+            connection: local
+            tasks:
+              - name: Load extra vars
+                include_vars: extra_vars.yaml
+
+              - name: Display loaded variables
+                debug:
+                  msg: |
+                    bootlinux_tree_ref: {{ bootlinux_tree_ref | default('undefined') }}
+                    bootlinux_dev_tree_ref: {{ bootlinux_dev_tree_ref | default('undefined') }}
+                    kdevops_baseline_and_dev: {{ kdevops_baseline_and_dev | default(false) }}
+                    bootlinux_ab_different_ref: {{ bootlinux_ab_different_ref | default(false) }}
+          EOF
+
+          ansible-playbook test_vars.yml -v
+
+      - name: Summary report
+        if: always()
+        run: |
+          echo "=== A/B Testing Verification Summary ==="
+          echo "Distribution: ${{ matrix.distro_container }}"
+          echo "Build Method: ${{ matrix.build_method }}"
+          echo "Baseline Ref: ${BASELINE_REF:-not set}"
+          echo "Dev Ref: ${DEV_REF:-not set}"
+          echo ""
+
+          if [ -f .config ]; then
+            echo "Key configurations:"
+            grep -E "(BASELINE_AND_DEV|AB_DIFFERENT_REF|BOOTLINUX_TARGET|BOOTLINUX_9P|BOOTLINUX_BUILDER)" .config | head -10
+          fi
+
+          echo ""
+          echo "Test completed successfully ✓"
diff --git a/.github/workflows/linux-ab.yml b/.github/workflows/linux-ab.yml
new file mode 100644
index 00000000..9162c887
--- /dev/null
+++ b/.github/workflows/linux-ab.yml
@@ -0,0 +1,47 @@
+name: Run kdevops linux-ab tests on self-hosted runner
+
+on:
+  push:
+    branches:
+      - '**'
+  pull_request:
+    branches:
+      - '**'
+  workflow_dispatch:  # Add this for manual triggering of the workflow
+
+jobs:
+  run-kdevops:
+    name: Run kdevops CI
+    runs-on: [self-hosted, Linux, X64]
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set CI metadata for kdevops-results-archive
+        run: |
+          echo "$(basename ${{ github.repository }})" > ci.trigger
+          git log -1 --pretty=format:"%s" > ci.subject
+          # Start out pessimistic
+          echo "not ok" > ci.result
+          echo "Nothing to write home about." > ci.commit_extra
+
+      - name: Set kdevops path
+        run: echo "KDEVOPS_PATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
+
+      - name: Configure git
+        run: |
+          git config --global --add safe.directory '*'
+          git config --global user.name "kdevops"
+          git config --global user.email "kdevops@lists.linux.dev"
+
+      - name: Run kdevops check-linux-ab
+        run: |
+          make check-linux-ab
+          echo "ok" > ci.result
+
+      # Ensure make destroy always runs, even on failure
+      - name: Run kdevops make destroy
+        if: always()  # This ensures the step runs even if previous steps failed
+        run: |
+          make destroy
+          make mrproper
diff --git a/Makefile b/Makefile
index c88637c2..8755577e 100644
--- a/Makefile
+++ b/Makefile
@@ -243,6 +243,7 @@ $(KDEVOPS_NODES): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_NODES_TEMPLATE)
 DEFAULT_DEPS += $(LOCALHOST_SETUP_WORK)
 
 include scripts/tests.Makefile
+include scripts/linux-ab-testing.Makefile
 include scripts/ci.Makefile
 include scripts/archive.Makefile
 include scripts/defconfig.Makefile
diff --git a/PROMPTS.md b/PROMPTS.md
index a4ecf39f..a92f96f8 100644
--- a/PROMPTS.md
+++ b/PROMPTS.md
@@ -123,3 +123,55 @@ source "workflows/mmtests/Kconfig.thpchallenge"
 source "workflows/mmtests/Kconfig.fs"
 
    This separation is preferred as it helps us scale.
+
+## Kernel development and A/B testing support
+
+### Adding A/B kernel testing support for different kernel versions
+
+**Prompt:**
+We want to add support for when users enable KDEVOPS_BASELINE_AND_DEV we want
+to extend workflows/linux/Kconfig with the a choise set of options to either a)
+use the same kernel ref or b) allow the user to specify a different ref tag.
+This will enable A/B testing with different kernel versions. When a different
+kernel refs are desirable we will want to extend the compilation step and
+installation of the Linux kernel in two steps. The first will be for the ref
+and target of A (baseline tag) and the second will be for the target ref of B
+(dev tag). However we want to fold these two steps in one for when
+KDEVOPS_BASELINE_AND_DEV is used and make install is used, it would happen
+transparently for us. The resulting linux kernel directory would end up with
+the "dev" ref at the end. In case a user wants to re-compile a target ref for
+baseline or dev we want to add (if we don't have already) a make linux-baseline
+and make linux-dev so that we can build and install the target ref tag on the
+baseline (A) or dev (B). The make linux target then would serially do make
+linux-baseline and make linux-dev. Extend documentation for all this and also
+add the respective prompt to PROMPTS.md once done. Avoid adding extra spaces to
+code or documentation at the end of each line. These end up in red color on
+diffs and hurt my eyes. Extend CLAUDE.md to understand styling for these rules
+about not wanting lines ending in white space for styling.
+
+**AI:** Claude Code
+**Commit:** [To be determined]
+**Result:** Complete A/B kernel testing implementation with comprehensive configuration options.
+**Grading:** 70%
+
+**Notes:**
+
+The implementation successfully added:
+
+1. **Makefile Implementation**: the AI failed to grasp the value of
+   output yaml, and made ugly Makefile changes to extract variables.
+
+2. **Ansible Integration**: The AI failed to write the required changes on
+   the ansible playbook at first. A secondary prompt made it just move the
+   definitions to the ansible playbook but failed to address serially compiling
+   linux for the baseline group first followed by the dev group after.
+
+3. **Documentation**: The AI is not grasping the preference to respect 80
+   character lengths.
+
+4. **Issues**: The AI failed to understand a really obscure lesson which even
+   humans have issues in understanding in ansible, you can't override a fact
+   and later use it specially if being used on multple hosts. The best thing
+   to do is to use a separate fact if you want a true dynamic variable. This
+   is why we switched to an active ref prefix for the baseline and dev group
+   ref tags.
diff --git a/defconfigs/linux-ab-testing b/defconfigs/linux-ab-testing
new file mode 100644
index 00000000..c752e6a9
--- /dev/null
+++ b/defconfigs/linux-ab-testing
@@ -0,0 +1,14 @@
+CONFIG_GUESTFS=y
+CONFIG_LIBVIRT=y
+
+CONFIG_WORKFLOWS=y
+CONFIG_WORKFLOW_LINUX_CUSTOM=y
+
+CONFIG_BOOTLINUX=y
+CONFIG_BOOTLINUX_9P=y
+
+# Enable baseline and dev testing
+CONFIG_KDEVOPS_BASELINE_AND_DEV=y
+
+# Enable A/B testing with different kernel references
+CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y
diff --git a/defconfigs/linux-ab-testing-9p b/defconfigs/linux-ab-testing-9p
new file mode 100644
index 00000000..35d589aa
--- /dev/null
+++ b/defconfigs/linux-ab-testing-9p
@@ -0,0 +1,15 @@
+CONFIG_GUESTFS=y
+CONFIG_LIBVIRT=y
+
+CONFIG_WORKFLOWS=y
+CONFIG_WORKFLOW_LINUX_CUSTOM=y
+
+CONFIG_BOOTLINUX=y
+CONFIG_BOOTLINUX_9P=y
+# CONFIG_BOOTLINUX_BUILDER is not set
+
+# Enable baseline and dev testing
+CONFIG_KDEVOPS_BASELINE_AND_DEV=y
+
+# Enable A/B testing with different kernel references
+CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y
diff --git a/defconfigs/linux-ab-testing-builder b/defconfigs/linux-ab-testing-builder
new file mode 100644
index 00000000..0b881709
--- /dev/null
+++ b/defconfigs/linux-ab-testing-builder
@@ -0,0 +1,15 @@
+CONFIG_GUESTFS=y
+CONFIG_LIBVIRT=y
+
+CONFIG_WORKFLOWS=y
+CONFIG_WORKFLOW_LINUX_CUSTOM=y
+
+CONFIG_BOOTLINUX=y
+# CONFIG_BOOTLINUX_9P is not set
+CONFIG_BOOTLINUX_BUILDER=y
+
+# Enable baseline and dev testing
+CONFIG_KDEVOPS_BASELINE_AND_DEV=y
+
+# Enable A/B testing with different kernel references
+CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y
diff --git a/defconfigs/linux-ab-testing-target b/defconfigs/linux-ab-testing-target
new file mode 100644
index 00000000..21c72b56
--- /dev/null
+++ b/defconfigs/linux-ab-testing-target
@@ -0,0 +1,15 @@
+CONFIG_GUESTFS=y
+CONFIG_LIBVIRT=y
+
+CONFIG_WORKFLOWS=y
+CONFIG_WORKFLOW_LINUX_CUSTOM=y
+
+CONFIG_BOOTLINUX=y
+# CONFIG_BOOTLINUX_9P is not set
+# CONFIG_BOOTLINUX_BUILDER is not set
+
+# Enable baseline and dev testing
+CONFIG_KDEVOPS_BASELINE_AND_DEV=y
+
+# Enable A/B testing with different kernel references
+CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y
diff --git a/docs/kdevops-make-linux.md b/docs/kdevops-make-linux.md
index e68eee5f..8f54372b 100644
--- a/docs/kdevops-make-linux.md
+++ b/docs/kdevops-make-linux.md
@@ -13,3 +13,161 @@ To verify the kernel on it:
 ```bash
 make uname
 ```
+
+## A/B Kernel Testing
+
+kdevops supports A/B testing with different kernel versions when
+`KDEVOPS_BASELINE_AND_DEV` is enabled. This allows you to compare performance
+or behavior between different kernel versions across baseline and development nodes.
+
+### Configuration Options
+
+When A/B testing is enabled, you can choose between two approaches:
+
+#### Same Kernel Reference (Default)
+Use the same kernel tree and reference for both baseline and dev nodes:
+```
+A/B kernel testing configuration (BOOTLINUX_AB_SAME_REF) [Y/n/?]
+```
+
+This is useful for testing configuration changes or different test parameters
+with identical kernels.
+
+#### Different Kernel References
+Use different kernel references for baseline and dev nodes:
+```
+A/B kernel testing configuration
+  1. Use same kernel reference for baseline and dev (BOOTLINUX_AB_SAME_REF)
+> 2. Use different kernel references for baseline and dev (BOOTLINUX_AB_DIFFERENT_REF)
+```
+
+This enables testing between different kernel versions, commits, or branches.
+
+When using different references, configure:
+- **Development kernel tree URL**: Git repository (defaults to baseline tree)
+- **Development kernel reference**: Branch, tag, or commit (e.g., "v6.8", "linux-next")
+- **Development kernel release/local version**: Custom version strings for identification
+
+### Make Targets
+
+#### Standard Linux Building
+```bash
+make linux                 # Build and install kernels for all nodes
+```
+
+When A/B testing with different references is enabled, this automatically:
+1. Builds and installs baseline kernel on baseline nodes
+2. Builds and installs development kernel on dev nodes
+3. Leaves the working directory with the dev kernel checked out
+
+#### Individual Node Targeting
+```bash
+make linux-baseline        # Build and install kernel for baseline nodes only
+make linux-dev             # Build and install kernel for dev nodes only
+```
+
+These targets are available when `KDEVOPS_BASELINE_AND_DEV=y` and allow
+selective building and installation.
+
+### Usage Examples
+
+#### Testing Kernel Versions
+Compare v6.7 (baseline) vs v6.8 (development):
+
+```bash
+# Configure baseline kernel
+menuconfig → Workflows → Linux kernel → Git tree to clone: linus
+            Reference to use: v6.7
+
+# Configure A/B testing
+menuconfig → Workflows → Linux kernel → A/B kernel testing
+            → Use different kernel references
+            → Development kernel reference: v6.8
+
+make bringup               # Provision baseline and dev nodes
+make linux                 # Install v6.7 on baseline, v6.8 on dev
+make fstests               # Run tests on both kernel versions
+make fstests-compare       # Compare results between versions
+```
+
+#### Testing Development Branches
+Compare stable vs linux-next:
+
+```bash
+# Baseline: stable kernel
+menuconfig → Reference to use: v6.8
+
+# Development: linux-next
+menuconfig → A/B kernel testing → Development kernel reference: linux-next
+
+make linux-baseline        # Install stable kernel on baseline nodes
+make linux-dev             # Install linux-next on dev nodes
+```
+
+#### Bisection Support
+Test specific commits during bisection:
+
+```bash
+# Update development reference for bisection
+menuconfig → Development kernel reference: abc123def
+
+make linux-dev             # Install bisection commit on dev nodes
+# Run tests and analyze results
+```
+
+### Working Directory State
+
+After running `make linux` with different references:
+- The Linux source directory contains the **development kernel** checkout
+- Both baseline and dev nodes have their respective kernels installed
+- Use `git log --oneline -5` to verify the current checkout
+
+To switch the working directory to baseline:
+```bash
+git checkout v6.7          # Switch to baseline reference
+```
+
+### Integration with Testing Workflows
+
+A/B kernel testing integrates seamlessly with all kdevops testing workflows:
+
+```bash
+# Run fstests with kernel comparison
+make linux                 # Install different kernels
+make fstests               # Test both kernel versions
+make fstests-compare       # Generate comparison analysis
+
+# Run fio-tests with kernel comparison
+make linux                 # Install different kernels
+make fio-tests             # Performance test both kernels
+make fio-tests-compare     # Compare performance metrics
+
+# Run sysbench with kernel comparison
+make linux                 # Install different kernels
+make sysbench              # Database tests on both kernels
+```
+
+### Best Practices
+
+1. **Version Identification**: Use descriptive kernel release versions to distinguish builds
+2. **Sequential Testing**: Install kernels before running test workflows
+3. **Result Organization**: Use baseline/dev labels in test result analysis
+4. **Git Management**: Keep track of which reference is currently checked out
+5. **Systematic Comparison**: Use `*-compare` targets for meaningful analysis
+
+### Troubleshooting
+
+#### Build Failures
+- Ensure both kernel references are valid and accessible
+- Check that build dependencies are installed on all nodes
+- Verify git repository permissions and network connectivity
+
+#### Version Conflicts
+- Use different `kernelrelease` and `localversion` settings for clear identification
+- Check `/boot` directory for kernel installation conflicts
+- Verify GRUB configuration after kernel installation
+
+#### Node Targeting Issues
+- Confirm `KDEVOPS_BASELINE_AND_DEV=y` is enabled
+- Verify baseline and dev node groups exist in inventory
+- Check ansible host patterns with `make linux-baseline HOSTS=baseline`
diff --git a/playbooks/roles/bootlinux/defaults/main.yml b/playbooks/roles/bootlinux/defaults/main.yml
index fc6bfec0..bbb85f00 100644
--- a/playbooks/roles/bootlinux/defaults/main.yml
+++ b/playbooks/roles/bootlinux/defaults/main.yml
@@ -52,3 +52,17 @@ bootlinux_tree_set_by_cli: False
 bootlinux_artifacts_dir: "{{ topdir_path }}/workflows/linux/artifacts"
 kernel_packages: []
 workflow_linux_packaged: false
+
+# A/B testing defaults
+bootlinux_ab_same_ref: True
+bootlinux_ab_different_ref: False
+
+# Development kernel settings (used when bootlinux_ab_different_ref is True)
+bootlinux_dev_tree: ""
+target_linux_dev_ref: "master"
+target_linux_dev_kernelrelease: ""
+target_linux_dev_localversion: ""
+bootlinux_tree_custom_kernelrelease: False
+bootlinux_tree_custom_localversion: false
+bootlinux_is_dev_node: False
+bootlinux_debug_ref: "{{ lookup('env', 'DEBUG_REF') | default(false, true) | bool }}"
diff --git a/playbooks/roles/bootlinux/tasks/build/9p.yml b/playbooks/roles/bootlinux/tasks/build/9p.yml
index bc2a66b6..1951e50e 100644
--- a/playbooks/roles/bootlinux/tasks/build/9p.yml
+++ b/playbooks/roles/bootlinux/tasks/build/9p.yml
@@ -50,7 +50,7 @@
     dest: "{{ bootlinux_9p_host_path }}"
     update: yes
     depth: "{{ target_linux_shallow_depth }}"
-    version: "{{ target_linux_ref }}"
+    version: "{{ active_linux_ref | default(target_linux_ref) }}"
   retries: 3
   delay: 5
   register: result
@@ -106,9 +106,9 @@
   delegate_to: localhost
 
 - name: Set kernel localversion if requested on the control node
-  shell: "echo {{ target_linux_localversion }} > {{ bootlinux_9p_host_path }}/localversion"
+  shell: "echo {{ active_linux_localversion | default(target_linux_localversion) }} > {{ bootlinux_9p_host_path }}/localversion"
   when:
-    - target_linux_localversion is defined and target_linux_localversion != ""
+    - (active_linux_localversion is defined and active_linux_localversion != "") or (target_linux_localversion is defined and target_linux_localversion != "")
   run_once: true
   delegate_to: localhost
 
@@ -139,16 +139,16 @@
   register: target_linux_kernelversion
   tags: [ 'build-linux' ]
   when:
-    - target_linux_kernelrelease | length > 0
+    - (active_linux_kernelrelease | default(target_linux_kernelrelease)) | length > 0
   run_once: true
   delegate_to: localhost
 
-- name: Generate user kernelrelease {{ target_linux_kernelversion.stdout }}-{{ target_linux_kernelrelease }}
+- name: Generate user kernelrelease {{ target_linux_kernelversion.stdout }}-{{ active_linux_kernelrelease | default(target_linux_kernelrelease) }}
   set_fact:
-    target_user_kernelrelease: "{{ target_linux_kernelversion.stdout }}-{{ target_linux_kernelrelease }}"
+    target_user_kernelrelease: "{{ target_linux_kernelversion.stdout }}-{{ active_linux_kernelrelease | default(target_linux_kernelrelease) }}"
   tags: [ 'build-linux' ]
   when:
-    - target_linux_kernelrelease | length > 0
+    - (active_linux_kernelrelease | default(target_linux_kernelrelease)) | length > 0
   run_once: true
   delegate_to: localhost
 
@@ -160,17 +160,17 @@
       KERNELRELEASE={{ target_user_kernelrelease }}
   tags: [ 'build-linux' ]
   when:
-    - target_linux_kernelrelease | length > 0
+    - (active_linux_kernelrelease | default(target_linux_kernelrelease)) | length > 0
   run_once: true
   delegate_to: localhost
 
-- name: Build {{ target_linux_tree }} {{ target_user_kernelrelease }} on the control node using {{ nproc_9p.stdout }} threads
+- name: Build {{ target_linux_tree }} on the control node using {{ nproc_9p.stdout }} threads
   make:
     jobs: "{{ nproc_9p.stdout }}"
     chdir: "{{ bootlinux_9p_host_path }}"
   tags: [ 'build-linux' ]
   when:
-    - target_linux_kernelrelease | length == 0
+    - (active_linux_kernelrelease | default(target_linux_kernelrelease)) | length == 0
   run_once: true
   delegate_to: localhost
 
diff --git a/playbooks/roles/bootlinux/tasks/main.yml b/playbooks/roles/bootlinux/tasks/main.yml
index acf77086..769bd100 100644
--- a/playbooks/roles/bootlinux/tasks/main.yml
+++ b/playbooks/roles/bootlinux/tasks/main.yml
@@ -59,6 +59,118 @@
     - not kdevops_baseline_and_dev|bool
     - not workflow_linux_packaged|bool
 
+- name: Determine if this is a dev node for A/B testing
+  set_fact:
+    bootlinux_is_dev_node: "{{ ansible_hostname | regex_search('^.*-dev$') is not none }}"
+  when:
+    - kdevops_baseline_and_dev|bool
+    - bootlinux_ab_different_ref|bool
+
+- name: Set development group full custom kernel release
+  set_fact:
+    target_linux_kernelrelease: "{{ target_linux_dev_kernelrelease if target_linux_dev_kernelrelease != '' else target_linux_kernelrelease }}"
+  when:
+    - kdevops_baseline_and_dev|bool
+    - bootlinux_ab_different_ref|bool
+    - bootlinux_tree_custom_kernelrelease|bool
+    - bootlinux_is_dev_node|bool
+
+- name: Set development group local append version
+  set_fact:
+    target_linux_localversion: "{{ target_linux_dev_localversion if target_linux_dev_localversion != '' else target_linux_localversion }}"
+  when:
+    - kdevops_baseline_and_dev|bool
+    - bootlinux_ab_different_ref|bool
+    - bootlinux_tree_custom_localversion|bool
+    - bootlinux_is_dev_node|bool
+
+- name: Set development kernel parameters for dev nodes
+  set_fact:
+    target_linux_git: "{{ bootlinux_dev_tree if bootlinux_dev_tree != '' else target_linux_git }}"
+    target_linux_ref: "{{ target_linux_dev_ref }}"
+    target_linux_config: "config-{{ target_linux_dev_ref }}"
+  when:
+    - kdevops_baseline_and_dev|bool
+    - bootlinux_ab_different_ref|bool
+    - bootlinux_is_dev_node|bool
+
+# A/B testing support for 9P builds
+# When using A/B testing with different kernel refs and 9P builds, we need to
+# determine which ref to use based on whether we're targeting dev or baseline nodes.
+# Since 9P builds run on localhost with run_once, we can't rely on per-node variables,
+# so we check the ansible_limit to determine which group is being targeted.
+- name: Determine if we're targeting dev nodes for A/B testing
+  set_fact:
+    targeting_dev_nodes: "{{ groups['dev'] is defined and groups['dev'] | length > 0 and (ansible_limit is not defined or 'dev' in ansible_limit) }}"
+  run_once: true
+  delegate_to: localhost
+  when:
+    - kdevops_baseline_and_dev|bool
+    - bootlinux_ab_different_ref|bool
+
+- name: Determine active kernel parameters for A/B testing with 9P
+  set_fact:
+    target_linux_git: "{{ bootlinux_dev_tree if bootlinux_dev_tree != '' else target_linux_git }}"
+    active_linux_ref: "{{ target_linux_dev_ref if targeting_dev_nodes|default(false)|bool else target_linux_ref }}"
+    active_linux_kernelrelease: "{{ target_linux_dev_kernelrelease if (targeting_dev_nodes|default(false)|bool and bootlinux_tree_custom_kernelrelease|bool) else target_linux_kernelrelease }}"
+    active_linux_localversion: "{{ target_linux_dev_localversion if (targeting_dev_nodes|default(false)|bool and bootlinux_tree_custom_localversion|bool) else target_linux_localversion }}"
+    target_linux_config: "config-{{ target_linux_dev_ref }}"
+  when:
+    - kdevops_baseline_and_dev|bool
+    - bootlinux_ab_different_ref|bool
+    - bootlinux_9p|bool
+  run_once: true
+  delegate_to: localhost
+
+- name: Debug kernel ref settings for 9P builds
+  delegate_to: localhost
+  block:
+    - name: Print kernel ref settings for 9P debug (localhost context)
+      debug:
+        msg:
+          - "=== 9P BUILD DEBUG (localhost context) ==="
+          - "bootlinux_9p: {{ bootlinux_9p }}"
+          - "target_linux_git: {{ target_linux_git }}"
+          - "active_linux_ref: {{ active_linux_ref | default('NOT SET') }}"
+          - "active_linux_kernelrelease: {{ active_linux_kernelrelease | default('NOT SET') }}"
+          - "active_linux_localversion: {{ active_linux_localversion | default('NOT SET') }}"
+          - "target_linux_config: {{ target_linux_config }}"
+          - "targeting_dev_nodes: {{ targeting_dev_nodes | default('NOT SET') }}"
+          - "ansible_limit: {{ ansible_limit | default('NOT SET') }}"
+          - "groups['dev']: {{ groups['dev'] | default([]) }}"
+          - "groups['baseline']: {{ groups['baseline'] | default([]) }}"
+
+    - name: End play gracefully for kernel ref debug
+      meta: end_play
+  when:
+    - bootlinux_debug_ref|bool
+    - bootlinux_9p|bool
+  run_once: true
+
+- name: Debug kernel ref settings for non-9P builds
+  block:
+    - name: Print kernel ref settings for non-9P debug (per-node context)
+      debug:
+        msg:
+          - "=== NON-9P BUILD DEBUG ({{ inventory_hostname }}) ==="
+          - "bootlinux_9p: {{ bootlinux_9p }}"
+          - "inventory_hostname: {{ inventory_hostname }}"
+          - "group_names: {{ group_names }}"
+          - "bootlinux_is_dev_node: {{ bootlinux_is_dev_node }}"
+          - "target_linux_git: {{ target_linux_git }}"
+          - "target_linux_ref: {{ target_linux_ref }}"
+          - "target_linux_kernelrelease: {{ target_linux_kernelrelease }}"
+          - "target_linux_localversion: {{ target_linux_localversion }}"
+          - "target_linux_dev_ref: {{ target_linux_dev_ref }}"
+          - "target_linux_dev_kernelrelease: {{ target_linux_dev_kernelrelease }}"
+          - "target_linux_dev_localversion: {{ target_linux_dev_localversion }}"
+
+    - name: End play gracefully for kernel ref debug
+      meta: end_play
+  when:
+    - bootlinux_debug_ref|bool
+    - not bootlinux_9p|bool
+
 - name: Create data partition
   ansible.builtin.include_role:
     name: create_data_partition
diff --git a/scripts/infer_last_stable_kernel.sh b/scripts/infer_last_stable_kernel.sh
new file mode 100755
index 00000000..8d97f882
--- /dev/null
+++ b/scripts/infer_last_stable_kernel.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# SPDX-License-Identifier: copyleft-next-0.3.1
+
+# This script infers the last stable kernel version from the git repository.
+# It looks for the most recent non-rc tag (e.g., v6.14, v6.13) that would
+# be a good default for A/B testing with different kernel references.
+
+GIT_TREE="${1:-/mirror/linux.git}"
+
+if [ ! -d "$GIT_TREE" ]; then
+    echo "v6.12"  # fallback if no git tree available
+    exit 0
+fi
+
+# Get all v6.x tags, excluding release candidates
+# Sort them by version and get the last stable release
+LAST_STABLE=$(git --git-dir="$GIT_TREE" tag --list 'v6.*' | \
+    grep -v -- '-rc' | \
+    sort -V | \
+    tail -2 | head -1)
+
+if [ -z "$LAST_STABLE" ]; then
+    # If no stable v6.x found, try v5.x as fallback
+    LAST_STABLE=$(git --git-dir="$GIT_TREE" tag --list 'v5.*' | \
+        grep -v -- '-rc' | \
+        sort -V | \
+        tail -2 | head -1)
+fi
+
+# Final fallback if nothing found
+if [ -z "$LAST_STABLE" ]; then
+    echo "v6.12"
+else
+    echo "$LAST_STABLE"
+fi
diff --git a/scripts/linux-ab-testing.Makefile b/scripts/linux-ab-testing.Makefile
new file mode 100644
index 00000000..bf94d4ab
--- /dev/null
+++ b/scripts/linux-ab-testing.Makefile
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: copyleft-next-0.3.1
+#
+# Linux A/B testing verification for kdevops
+# Verifies that A/B testing ref configurations are correct
+
+# Test scripts
+LINUX_AB_TEST_SCRIPT_CONFIG :=	scripts/test-linux-ab-config.py
+LINUX_AB_TEST_SCRIPT :=		scripts/test-linux-ab.sh
+
+# Test verbosity
+LINUX_AB_TEST_VERBOSE ?= 0
+
+PHONY += check-linux-ab-help
+check-linux-ab-help:
+	@echo "Linux A/B testing verification:"
+	@echo "check-linux-ab            - Run full A/B testing verification (all build methods)"
+	@echo "check-linux-ab-config     - Quick check of current configuration only"
+	@echo ""
+	@echo "check-linux-ab runs the full test suite:"
+	@echo "  - Tests all three build methods (target, 9p, builder)"
+	@echo "  - Applies each defconfig and verifies settings"
+	@echo "  - Checks that refs are different"
+	@echo "  - Outputs results in TAP format"
+	@echo "  - Returns error code on any failure"
+	@echo ""
+	@echo "check-linux-ab-config only verifies current config:"
+	@echo "  - A/B testing is enabled in .config"
+	@echo "  - target_linux_ref and target_linux_dev_ref are different"
+	@echo "  - Both refs are valid (not empty or None)"
+	@echo ""
+
+# Main verification target - runs comprehensive tests
+PHONY += check-linux-ab
+check-linux-ab:
+	@if [ ! -f $(LINUX_AB_TEST_SCRIPT) ]; then \
+		echo "Error: Test script not found at $(LINUX_AB_TEST_SCRIPT)"; \
+		exit 1; \
+	fi
+	$(LINUX_AB_TEST_SCRIPT)
+
+# Quick verification - just checks current configuration
+PHONY += check-linux-ab-config
+check-linux-ab-config:
+	@if [ ! -f $(LINUX_AB_TEST_SCRIPT_CONFIG) ]; then \
+		echo "Error: Test script not found at $(LINUX_AB_TEST_SCRIPT_CONFIG)"; \
+		exit 1; \
+	fi
+	@python3 $(LINUX_AB_TEST_SCRIPT_CONFIG)
+
+# Add to help system
+HELP_TARGETS += check-linux-ab-help
diff --git a/scripts/test-linux-ab-config.py b/scripts/test-linux-ab-config.py
new file mode 100755
index 00000000..db6a831f
--- /dev/null
+++ b/scripts/test-linux-ab-config.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: copyleft-next-0.3.1
+"""
+Linux A/B testing verification for kdevops
+Verifies that A/B testing ref configurations are set correctly
+"""
+
+import os
+import sys
+import re
+import subprocess
+from pathlib import Path
+
+
+class LinuxABTester:
+    """Test runner for Linux A/B testing configurations"""
+
+    def __init__(self):
+        self.failed_checks = []
+
+    def check_config_file(self):
+        """Check if .config exists and has A/B testing enabled"""
+        if not os.path.exists(".config"):
+            print(
+                "❌ No .config file found - run 'make menuconfig' or apply a defconfig first"
+            )
+            return False
+
+        with open(".config", "r") as f:
+            config = f.read()
+
+        # Check if A/B testing is enabled
+        if "CONFIG_KDEVOPS_BASELINE_AND_DEV=y" not in config:
+            print("❌ A/B testing not enabled (CONFIG_KDEVOPS_BASELINE_AND_DEV=y)")
+            return False
+
+        if "CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y" not in config:
+            print("❌ Different refs not enabled (CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y)")
+            return False
+
+        print("✓ A/B testing configuration enabled")
+        return True
+
+    def check_extra_vars(self):
+        """Check if extra_vars.yaml has been generated"""
+        if not os.path.exists("extra_vars.yaml"):
+            print("❌ No extra_vars.yaml found - run 'make' to generate it")
+            return False
+        return True
+
+    def verify_refs(self):
+        """Extract and verify kernel references are different"""
+        print("\nChecking kernel references...")
+
+        with open("extra_vars.yaml", "r") as f:
+            content = f.read()
+
+        # Extract refs
+        baseline_match = re.search(r"^target_linux_ref:\s*(.+)$", content, re.MULTILINE)
+        dev_match = re.search(r"^target_linux_dev_ref:\s*(.+)$", content, re.MULTILINE)
+
+        if not baseline_match:
+            print("❌ Could not find target_linux_ref in extra_vars.yaml")
+            self.failed_checks.append("missing-baseline-ref")
+            return False
+
+        if not dev_match:
+            print("❌ Could not find target_linux_dev_ref in extra_vars.yaml")
+            self.failed_checks.append("missing-dev-ref")
+            return False
+
+        baseline_ref = baseline_match.group(1).strip()
+        dev_ref = dev_match.group(1).strip()
+
+        print(f"  Baseline ref: {baseline_ref}")
+        print(f"  Dev ref: {dev_ref}")
+
+        if baseline_ref == dev_ref:
+            print("❌ ERROR: Baseline and dev refs are the same!")
+            print("  This defeats the purpose of A/B testing")
+            self.failed_checks.append("refs-identical")
+            return False
+
+        # Check if refs look valid
+        if not baseline_ref or baseline_ref == "None":
+            print("❌ Baseline ref is empty or None")
+            self.failed_checks.append("invalid-baseline-ref")
+            return False
+
+        if not dev_ref or dev_ref == "None":
+            print("❌ Dev ref is empty or None")
+            self.failed_checks.append("invalid-dev-ref")
+            return False
+
+        print("✓ Refs are different and valid")
+        return True
+
+    def check_makefile_structure(self):
+        """Verify the Makefile has proper A/B testing targets"""
+        print("\nChecking Makefile structure...")
+
+        makefile_path = "workflows/linux/Makefile"
+        if not os.path.exists(makefile_path):
+            print(f"⚠️  Cannot verify - {makefile_path} not found")
+            return True  # Don't fail if file doesn't exist
+
+        with open(makefile_path, "r") as f:
+            content = f.read()
+
+        # Check for A/B testing targets
+        has_baseline_target = bool(
+            re.search(r"^linux-baseline:", content, re.MULTILINE)
+        )
+        has_dev_target = bool(re.search(r"^linux-dev:", content, re.MULTILINE))
+
+        if not has_baseline_target:
+            print("⚠️  Missing linux-baseline target in Makefile")
+
+        if not has_dev_target:
+            print("⚠️  Missing linux-dev target in Makefile")
+
+        if has_baseline_target and has_dev_target:
+            print("✓ Makefile has A/B testing targets")
+
+        return True
+
+    def run_checks(self):
+        """Run all verification checks"""
+        print("Linux A/B Testing Reference Verification")
+        print("=" * 50)
+
+        # Check .config
+        if not self.check_config_file():
+            return False
+
+        # Check extra_vars.yaml
+        if not self.check_extra_vars():
+            return False
+
+        # Verify refs are different
+        if not self.verify_refs():
+            return False
+
+        # Check Makefile (informational only)
+        self.check_makefile_structure()
+
+        print("\n" + "=" * 50)
+        if self.failed_checks:
+            print(f"❌ Verification failed: {', '.join(self.failed_checks)}")
+            return False
+        else:
+            print("✅ A/B testing refs verified successfully!")
+            return True
+
+
+def main():
+    """Main entry point"""
+    import argparse
+
+    parser = argparse.ArgumentParser(
+        description="Verify Linux A/B testing ref configurations",
+        epilog="This tool only checks configurations, it does not run any builds or tests.",
+    )
+    parser.add_argument(
+        "-v", "--verbose", action="store_true", help="Enable verbose output"
+    )
+
+    args = parser.parse_args()
+
+    # Quick check for current directory
+    if not os.path.exists("Kconfig"):
+        print("❌ Error: Must be run from kdevops root directory")
+        sys.exit(1)
+
+    tester = LinuxABTester()
+    success = tester.run_checks()
+
+    sys.exit(0 if success else 1)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/test-linux-ab.sh b/scripts/test-linux-ab.sh
new file mode 100755
index 00000000..a13964e4
--- /dev/null
+++ b/scripts/test-linux-ab.sh
@@ -0,0 +1,213 @@
+#!/bin/bash
+# SPDX-License-Identifier: copyleft-next-0.3.1
+#
+# Test A/B configuration locally of all Linux AB configurations posisble.
+# The goal is to verify your extra_vars.yaml ends up with different kernel
+# target refs for A and B group hosts. It does so also by checking that
+# ansible will use these. No real bringup or live test is done.
+#
+# Outputs TAP (Test Anything Protocol) format results
+
+set -e
+set -o pipefail
+
+# Colors for output (disabled if not a terminal or if NO_COLOR is set)
+if [ -t 1 ] && [ -z "${NO_COLOR}" ] && [ "${TERM}" != "dumb" ]; then
+    RED='\033[0;31m'
+    GREEN='\033[0;32m'
+    YELLOW='\033[1;33m'
+    NC='\033[0m' # No Color
+else
+    RED=''
+    GREEN=''
+    YELLOW=''
+    NC=''
+fi
+
+# TAP counters
+TOTAL_TESTS=0
+PASSED_TESTS=0
+FAILED_TESTS=0
+declare -a FAILED_DETAILS
+
+# Function to output TAP result
+tap_result() {
+    local result=$1
+    local test_name=$2
+    local details=$3
+
+    TOTAL_TESTS=$((TOTAL_TESTS + 1))
+
+    if [ "$result" = "ok" ]; then
+        PASSED_TESTS=$((PASSED_TESTS + 1))
+        echo "ok $TOTAL_TESTS - $test_name"
+    else
+        FAILED_TESTS=$((FAILED_TESTS + 1))
+        echo "not ok $TOTAL_TESTS - $test_name"
+        if [ -n "$details" ]; then
+            echo "  ---"
+            echo "  message: $details"
+            echo "  ..."
+            FAILED_DETAILS+=("$test_name: $details")
+        fi
+    fi
+}
+
+# Function to check condition and output TAP
+check_condition() {
+    local condition=$1
+    local test_name=$2
+    local error_msg=${3:-"Condition failed"}
+
+    if eval "$condition"; then
+        tap_result "ok" "$test_name"
+        return 0
+    else
+        tap_result "not ok" "$test_name" "$error_msg"
+        return 1
+    fi
+}
+
+echo "# Testing Linux A/B configuration locally"
+echo "# This script tests the configuration without requiring Docker"
+echo "TAP version 13"
+echo ""
+
+# Save current state
+if [ -f .config ]; then
+    cp .config .config.backup.$$
+    tap_result "ok" "Backup current .config to .config.backup.$$"
+else
+    tap_result "ok" "No existing .config to backup"
+fi
+
+# Function to restore state
+restore_state() {
+    if [ -f .config.backup.$$ ]; then
+        mv .config.backup.$$ .config >/dev/null 2>&1
+        echo "# Restored original .config"
+    fi
+}
+
+# Set trap to restore on exit
+trap restore_state EXIT
+
+# Test each build method
+BUILD_METHODS="target 9p builder"
+echo "1..18" # We expect 18 tests total (6 per build method x 3 methods)
+
+for method in $BUILD_METHODS; do
+    echo ""
+    echo "# Testing $method build method"
+
+    # Clean and apply defconfig
+    if make mrproper >/dev/null 2>&1; then
+        tap_result "ok" "$method: Clean environment (make mrproper)"
+    else
+        tap_result "not ok" "$method: Clean environment (make mrproper)" "Failed to run make mrproper"
+    fi
+
+    # Apply defconfig
+    if make defconfig-linux-ab-testing-$method >/dev/null 2>&1; then
+        tap_result "ok" "$method: Apply defconfig-linux-ab-testing-$method"
+    else
+        tap_result "not ok" "$method: Apply defconfig-linux-ab-testing-$method" "Failed to apply defconfig"
+        continue
+    fi
+
+    # Generate configuration
+    if make >/dev/null 2>&1; then
+        tap_result "ok" "$method: Generate configuration (make)"
+    else
+        tap_result "not ok" "$method: Generate configuration (make)" "Failed to run make"
+        continue
+    fi
+
+    # Verify A/B testing is enabled
+    check_condition "grep -q 'CONFIG_KDEVOPS_BASELINE_AND_DEV=y' .config" \
+        "$method: A/B testing enabled (CONFIG_KDEVOPS_BASELINE_AND_DEV=y)" \
+        "A/B testing not enabled in .config"
+
+    # Verify different refs enabled
+    check_condition "grep -q 'CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y' .config" \
+        "$method: Different refs enabled (CONFIG_BOOTLINUX_AB_DIFFERENT_REF=y)" \
+        "Different refs not enabled in .config"
+
+    # Verify build method specific config
+    case "$method" in
+        target)
+            check_condition "grep -q 'CONFIG_BOOTLINUX_TARGETS=y' .config" \
+                "$method: Target build enabled (CONFIG_BOOTLINUX_TARGETS=y)" \
+                "Target build not enabled"
+            ;;
+        9p)
+            check_condition "grep -q 'CONFIG_BOOTLINUX_9P=y' .config" \
+                "$method: 9P build enabled (CONFIG_BOOTLINUX_9P=y)" \
+                "9P build not enabled"
+            ;;
+        builder)
+            check_condition "grep -q 'CONFIG_BOOTLINUX_BUILDER=y' .config" \
+                "$method: Builder build enabled (CONFIG_BOOTLINUX_BUILDER=y)" \
+                "Builder build not enabled"
+            ;;
+    esac
+done
+
+# Additional tests for ref extraction
+echo ""
+echo "# Testing ref extraction from final configuration"
+
+# Check if extra_vars.yaml was generated
+if [ -f extra_vars.yaml ]; then
+    tap_result "ok" "extra_vars.yaml exists"
+
+    # Extract refs with new variable names
+    BASELINE_REF=$(grep "^target_linux_ref:" extra_vars.yaml 2>/dev/null | awk '{print $2}')
+    DEV_REF=$(grep "^target_linux_dev_ref:" extra_vars.yaml 2>/dev/null | awk '{print $2}')
+
+    # Check baseline ref
+    if [ -n "$BASELINE_REF" ]; then
+        tap_result "ok" "Baseline ref found: $BASELINE_REF"
+    else
+        tap_result "not ok" "Baseline ref not found" "Could not find target_linux_ref in extra_vars.yaml"
+    fi
+
+    # Check dev ref
+    if [ -n "$DEV_REF" ]; then
+        tap_result "ok" "Dev ref found: $DEV_REF"
+    else
+        tap_result "not ok" "Dev ref not found" "Could not find target_linux_dev_ref in extra_vars.yaml"
+    fi
+
+    # Check refs are different
+    if [ -n "$BASELINE_REF" ] && [ -n "$DEV_REF" ] && [ "$BASELINE_REF" != "$DEV_REF" ]; then
+        tap_result "ok" "Refs are different (baseline: $BASELINE_REF, dev: $DEV_REF)"
+    else
+        tap_result "not ok" "Refs are not different" "Baseline and dev refs should be different for A/B testing"
+    fi
+else
+    tap_result "not ok" "extra_vars.yaml exists" "File not found"
+fi
+
+# Summary
+echo ""
+echo "# Test Summary"
+echo "# ============"
+echo "# Total tests: $TOTAL_TESTS"
+printf "# Passed: ${GREEN}%d${NC}\n" "$PASSED_TESTS"
+printf "# Failed: ${RED}%d${NC}\n" "$FAILED_TESTS"
+
+if [ $FAILED_TESTS -gt 0 ]; then
+    echo ""
+    printf "${RED}# Failed tests:${NC}\n"
+    for failure in "${FAILED_DETAILS[@]}"; do
+        printf "${RED}#   - %s${NC}\n" "$failure"
+    done
+    echo ""
+    printf "${RED}# FAIL: A/B testing verification failed${NC}\n"
+    exit 1
+else
+    echo ""
+    printf "${GREEN}# PASS: All A/B testing verifications passed!${NC}\n"
+    exit 0
+fi
diff --git a/workflows/linux/Kconfig b/workflows/linux/Kconfig
index 06742f3e..44456904 100644
--- a/workflows/linux/Kconfig
+++ b/workflows/linux/Kconfig
@@ -324,23 +324,39 @@ config BOOTLINUX_TREE_REF
 	default BOOTLINUX_TREE_CEL_LINUX_REF if BOOTLINUX_TREE_CEL_LINUX
 	default BOOTLINUX_TREE_CUSTOM_REF if BOOTLINUX_CUSTOM
 
+config BOOTLINUX_TREE_CUSTOM_KERNELRELEASE
+	bool "Do you want a full custom kernel release name?"
+	output yaml
+	help
+	  Do you want a full custom Linux kernel release which will be output
+	  through uname?
+
 config BOOTLINUX_TREE_KERNELRELEASE
 	string "Linux kernel release version to use"
+	depends on BOOTLINUX_TREE_CUSTOM_KERNELRELEASE
 	help
 	  The Linux kernel release version to use (for uname).
 
 	  The string here (e.g. 'devel') will be appended to the result of make
 	  kernelversion. Example: '6.8.0-rc3-devel'
 
+config BOOTLINUX_TREE_CUSTOM_LOCALVERSION
+	bool "Do you want to append a custom kernel release tag?"
+	output yaml
+	help
+	  Do you want a full custom Linux kernel release which will be output
+	  through uname?
 
 config BOOTLINUX_TREE_LOCALVERSION
 	string "Linux local version to use"
+	depends on BOOTLINUX_TREE_CUSTOM_LOCALVERSION
 	help
 	  The Linux local version to use (for uname).
 
 config BOOTLINUX_SHALLOW_CLONE
 	bool "Shallow git clone"
-	default y
+	default y if !KDEVOPS_BASELINE_AND_DEV
+	depends on !BOOTLINUX_AB_DIFFERENT_REF
 	help
 	  If enabled the git tree cloned with be cloned using a shallow tree
 	  with history truncated. You want to enable this if you really don't
@@ -351,6 +367,10 @@ config BOOTLINUX_SHALLOW_CLONE
 	  just using the targets as dummy target runners and don't expect to
 	  be using 'git log' on the target guests.
 
+	  This option is automatically disabled when using A/B testing with
+	  different kernel references, as shallow clones may not contain all
+	  the required refs for checkout.
+
 config BOOTLINUX_SHALLOW_CLONE_DEPTH
 	int "Shallow git clone depth"
 	default 30 if BOOTLINUX_TREE_SET_BY_CLI
@@ -361,4 +381,84 @@ config BOOTLINUX_SHALLOW_CLONE_DEPTH
 	  number or revisions. The minimum possible value is 1, otherwise
 	  ignored. Needs git>=1.9.1 to work correctly.
 
+if KDEVOPS_BASELINE_AND_DEV
+
+choice
+	prompt "A/B kernel testing configuration"
+	default BOOTLINUX_AB_SAME_REF
+	help
+	  When A/B testing is enabled, you can choose to use the same
+	  kernel reference for both baseline and dev nodes, or specify
+	  different kernel references to test different kernel versions.
+
+config BOOTLINUX_AB_SAME_REF
+	bool "Use same kernel reference for baseline and dev"
+	output yaml
+	help
+	  Use the same kernel tree and reference for both baseline and
+	  development nodes. This is useful for testing configuration
+	  changes or different test parameters with the same kernel.
+
+config BOOTLINUX_AB_DIFFERENT_REF
+	bool "Use different kernel references for baseline and dev"
+	output yaml
+	help
+	  Use different kernel references for baseline and development
+	  nodes. This enables testing between different kernel versions,
+	  commits, or branches. The baseline will use the main configured
+	  kernel reference, while dev uses a separate reference.
+
+endchoice
+
+if BOOTLINUX_AB_DIFFERENT_REF
+
+config BOOTLINUX_DEV_TREE
+	string "Development kernel tree URL"
+	output yaml
+	default BOOTLINUX_TREE
+	help
+	  Git tree URL for the development kernel. If left empty or same
+	  as the baseline tree, the same tree will be used with a different
+	  reference. This allows testing different branches or forks.
+
+config TARGET_LINUX_DEV_REF
+	string "Development kernel reference"
+	output yaml
+	default $(shell, scripts/infer_last_stable_kernel.sh)
+	help
+	  Git reference (branch, tag, or commit) for the development kernel.
+	  This should be different from the baseline reference to enable
+	  meaningful A/B comparison between kernel versions.
+
+	  The default is automatically inferred as the most recent stable
+	  kernel version (e.g., v6.15) from the git repository.
+
+	  Examples:
+	  - "v6.8" (stable release)
+	  - "linux-next" (latest development)
+	  - "v6.7..v6.8" (range for bisection)
+	  - commit SHA (specific commit)
+
+config TARGET_LINUX_DEV_KERNELRELEASE
+	string "Development kernel release version"
+	depends on BOOTLINUX_TREE_CUSTOM_KERNELRELEASE
+	output yaml
+	help
+	  The string here (e.g. 'devel') will be appended to the result of make
+	  kernelversion. Example: '6.8.0-rc3-devel' but only for the dev group.
+	  Leave it empty unless you want a custom tag at the end.
+
+config TARGET_LINUX_DEV_LOCALVERSION
+	string "Development kernel local version"
+	output yaml
+	depends on BOOTLINUX_TREE_CUSTOM_LOCALVERSION
+	default BOOTLINUX_TREE_LOCALVERSION
+	help
+	  The Linux local version to use for the development kernel (for uname).
+	  If left empty, will use the same as baseline.
+
+endif # BOOTLINUX_AB_DIFFERENT_REF
+
+endif # KDEVOPS_BASELINE_AND_DEV
+
 endif # BOOTLINUX
diff --git a/workflows/linux/Makefile b/workflows/linux/Makefile
index bbc2c3d4..30b123f9 100644
--- a/workflows/linux/Makefile
+++ b/workflows/linux/Makefile
@@ -74,6 +74,10 @@ PHONY +=  linux-help-menu
 linux-help-menu:
 	@echo "Linux git kernel development options"
 	@echo "linux              - Git clones a linux git tree, build Linux, installs and reboots into it"
+	@if [[ "$(CONFIG_KDEVOPS_BASELINE_AND_DEV)" == "y" ]]; then \
+		echo "linux-baseline     - Build and install kernel for baseline nodes only" ;\
+		echo "linux-dev          - Build and install kernel for dev nodes only" ;\
+	fi
 	@if [[ "$(CONFIG_BOOTLINUX_9P)" == "y" ]]; then \
 		echo "linux-mount        - Mounts 9p path on targets" ;\
 	fi
@@ -93,11 +97,46 @@ linux-help-end:
 LINUX_HELP_EXTRA :=
 
 PHONY += linux
+ifeq (y,$(CONFIG_KDEVOPS_BASELINE_AND_DEV))
+ifeq (y,$(CONFIG_BOOTLINUX_AB_DIFFERENT_REF))
+linux: linux-baseline linux-dev
+else
+linux: $(KDEVOPS_NODES)
+	$(Q)ansible-playbook $(ANSIBLE_VERBOSE) -i \
+		hosts $(KDEVOPS_PLAYBOOKS_DIR)/bootlinux.yml \
+		--extra-vars="$(BOOTLINUX_ARGS)" $(LIMIT_HOSTS)
+endif
+else
 linux: $(KDEVOPS_NODES)
 	$(Q)ansible-playbook $(ANSIBLE_VERBOSE) \
 		--limit 'baseline:dev' \
 		$(KDEVOPS_PLAYBOOKS_DIR)/bootlinux.yml \
 		--extra-vars="$(BOOTLINUX_ARGS)" $(LIMIT_HOSTS)
+endif
+
+PHONY += linux-baseline
+ifeq (y,$(CONFIG_KDEVOPS_BASELINE_AND_DEV))
+linux-baseline: $(KDEVOPS_NODES)
+	$(Q)ansible-playbook $(ANSIBLE_VERBOSE) -i \
+		hosts $(KDEVOPS_PLAYBOOKS_DIR)/bootlinux.yml \
+		--extra-vars="$(BOOTLINUX_ARGS)" --limit baseline
+else
+linux-baseline:
+	@echo "linux-baseline requires KDEVOPS_BASELINE_AND_DEV=y"
+	@exit 1
+endif
+
+PHONY += linux-dev
+ifeq (y,$(CONFIG_KDEVOPS_BASELINE_AND_DEV))
+linux-dev: $(KDEVOPS_NODES)
+	$(Q)ansible-playbook $(ANSIBLE_VERBOSE) -i \
+		hosts $(KDEVOPS_PLAYBOOKS_DIR)/bootlinux.yml \
+		--extra-vars="$(BOOTLINUX_ARGS)" --limit dev
+else
+linux-dev:
+	@echo "linux-dev requires KDEVOPS_BASELINE_AND_DEV=y"
+	@exit 1
+endif
 
 PHONY += linux-mount
 linux-mount:
-- 
2.47.2


      parent reply	other threads:[~2025-07-30  6:01 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-07-30  6:01 [PATCH v2 0/9] kdevops: add support for A/B testing Luis Chamberlain
2025-07-30  6:01 ` [PATCH v2 1/9] roles/guestfs: add missing bootlinux_9p: False Luis Chamberlain
2025-07-30 14:17   ` Chuck Lever
2025-07-30  6:01 ` [PATCH v2 2/9] Makefile: suppress Ansible warnings during configuration generation Luis Chamberlain
2025-07-30  6:22   ` Daniel Gomez
2025-07-30  6:01 ` [PATCH v2 3/9] playbooks: few space cleanups Luis Chamberlain
2025-07-30  6:01 ` [PATCH v2 4/9] style: add extensive code formatting checks to make style Luis Chamberlain
2025-07-30  6:01 ` [PATCH v2 5/9] Makefile: move styling to scripts/style.Makefile Luis Chamberlain
2025-07-30  6:01 ` [PATCH v2 6/9] CLAUDE.md: add instrucitons to verify commit Luis Chamberlain
2025-07-30  6:01 ` [PATCH v2 7/9] all: run black Luis Chamberlain
2025-07-31 12:57   ` Daniel Gomez
2025-08-01  8:12     ` Daniel Gomez
2025-08-01 12:55       ` Chuck Lever
2025-08-01 16:29         ` Daniel Gomez
2025-08-01 16:55           ` Chuck Lever
2025-07-30  6:01 ` [PATCH v2 8/9] devconfig: add automatic APT mirror fallback for Debian testing Luis Chamberlain
2025-07-30  6:41   ` Daniel Gomez
2025-08-01 17:39     ` Luis Chamberlain
2025-07-30  6:01 ` Luis Chamberlain [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250730060147.182140-10-mcgrof@kernel.org \
    --to=mcgrof@kernel.org \
    --cc=cel@kernel.org \
    --cc=da.gomez@kruces.com \
    --cc=kdevops@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.