diff mbox series

[9/9] tests/setup: add test_update_rebase_strategy

Message ID 20260322193440.870120-10-adrian.freihofer@siemens.com
State New
Headers show
Series bitbake-setup: improvements, VSCode workspace generation | expand

Commit Message

AdrianF March 22, 2026, 7:34 p.m. UTC
From: Adrian Freihofer <adrian.freihofer@siemens.com>

Add a dedicated test for the --rebase-strategy option introduced in
'bitbake-setup: add --rebase-strategy to the update command'.

Three scenarios are covered that are not exercised by the existing
test_setup:

1. Uncommitted tracked-file change, default 'stop' strategy: update
   must fail with an error message that names the source and hints at
   --rebase-strategy=backup; no backup directory is created.

2. Same uncommitted change, 'backup' strategy: the layer directory is
   renamed to a timestamped backup, the layer is re-cloned from
   upstream, and the result is clean (upstream content, no local
   modifications).

3. Committed local change that conflicts with the next upstream commit,
   'backup' strategy: instead of the hard rebase-conflict failure that
   occurs with the default strategy, the conflicted directory is backed
   up and re-cloned successfully.

A small _count_layer_backups() helper is added to the class and reused
across scenarios.

Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
 lib/bb/tests/setup.py | 88 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)
diff mbox series

Patch

diff --git a/lib/bb/tests/setup.py b/lib/bb/tests/setup.py
index 1bcefd698..6339fd5ac 100644
--- a/lib/bb/tests/setup.py
+++ b/lib/bb/tests/setup.py
@@ -640,3 +640,91 @@  print("BBPATH is {{}}".format(os.environ["BBPATH"]))
         del os.environ['BBPATH']
         self.assertTrue(os.path.exists(workspace_file),
                         "bitbake.code-workspace should be recreated by update --init-vscode even when nothing changed")
+
+    def _count_layer_backups(self, layers_path):
+        return len([f for f in os.listdir(layers_path) if 'backup' in f])
+
+    def test_update_rebase_strategy(self):
+        """Test the --rebase-strategy option for the update command.
+
+        Covers three scenarios not exercised by test_setup:
+        1. Uncommitted tracked-file change + default 'stop' strategy → clean error
+           message that names the source and mentions --rebase-strategy=backup.
+        2. Uncommitted tracked-file change + 'backup' strategy → directory is
+           renamed to a timestamped backup and the layer is re-cloned cleanly.
+        3. Committed local change that would cause a rebase conflict + 'backup'
+           strategy → backup + re-clone instead of a hard failure.
+        """
+        if 'BBPATH' in os.environ:
+            del os.environ['BBPATH']
+        os.chdir(self.tempdir)
+
+        self.runbbsetup("settings set default registry 'git://{};protocol=file;branch=master;rev=master'".format(self.registrypath))
+        self.add_file_to_testrepo('test-file', 'initial\n')
+        self.add_json_config_to_registry('test-config-1.conf.json', 'master', 'master')
+        self.runbbsetup("init --non-interactive test-config-1 gadget")
+
+        setuppath = self.get_setup_path('test-config-1', 'gadget')
+        layer_path = os.path.join(setuppath, 'layers', 'test-repo')
+        layers_path = os.path.join(setuppath, 'layers')
+
+        # Scenario 1: uncommitted tracked change, default 'stop' strategy
+        # Advance upstream so an update is required.
+        self.add_file_to_testrepo('test-file', 'upstream-v2\n')
+        # Modify the same tracked file in the layer without committing.
+        with open(os.path.join(layer_path, 'test-file'), 'w') as f:
+            f.write('locally-modified\n')
+
+        os.environ['BBPATH'] = os.path.join(setuppath, 'build')
+        with self.assertRaises(bb.process.ExecutionError) as ctx:
+            self.runbbsetup("update --update-bb-conf='no'")
+        self.assertIn('has local modifications', str(ctx.exception))
+        self.assertIn('--rebase-strategy=backup', str(ctx.exception))
+        # No backup directory must have been created.
+        self.assertEqual(self._count_layer_backups(layers_path), 0,
+                         "stop strategy must not create any backup")
+
+        # Scenario 2: same uncommitted change, 'backup' strategy
+        out = self.runbbsetup("update --update-bb-conf='no' --rebase-strategy=backup")
+        # One backup directory must now exist.
+        self.assertEqual(self._count_layer_backups(layers_path), 1,
+                         "backup strategy must create exactly one backup")
+        # The re-cloned layer must be clean and at the upstream revision.
+        with open(os.path.join(layer_path, 'test-file')) as f:
+            self.assertEqual(f.read(), 'upstream-v2\n',
+                             "re-cloned layer must contain the upstream content")
+        status = self.git('status --porcelain', cwd=layer_path).strip()
+        self.assertEqual(status, '',
+                         "re-cloned layer must have no local modifications")
+        del os.environ['BBPATH']
+
+        # Scenario 3: committed conflicting change, 'backup' strategy
+        # Re-initialise a fresh setup so we start from a clean state.
+        self.runbbsetup("init --non-interactive --setup-dir-name rebase-conflict-setup test-config-1 gadget")
+        conflict_setup = os.path.join(self.tempdir, 'bitbake-builds', 'rebase-conflict-setup')
+        conflict_layer = os.path.join(conflict_setup, 'layers', 'test-repo')
+        conflict_layers = os.path.join(conflict_setup, 'layers')
+
+        # Commit a local change that touches the same file as the next upstream commit.
+        with open(os.path.join(conflict_layer, 'test-file'), 'w') as f:
+            f.write('conflicting-local\n')
+        self.git('add test-file', cwd=conflict_layer)
+        self.git('commit -m "Local conflicting change"', cwd=conflict_layer)
+
+        # Advance upstream with a conflicting edit.
+        self.add_file_to_testrepo('test-file', 'conflicting-upstream\n')
+
+        os.environ['BBPATH'] = os.path.join(conflict_setup, 'build')
+        # Default stop strategy must still fail with a conflict error.
+        with self.assertRaisesRegex(bb.process.ExecutionError, "Merge conflict in test-file"):
+            self.runbbsetup("update --update-bb-conf='no'")
+        self.assertEqual(self._count_layer_backups(conflict_layers), 0)
+
+        # Backup strategy must succeed: backup the conflicted dir and re-clone.
+        self.runbbsetup("update --update-bb-conf='no' --rebase-strategy=backup")
+        self.assertEqual(self._count_layer_backups(conflict_layers), 1,
+                         "backup strategy must create exactly one backup after a conflict")
+        with open(os.path.join(conflict_layer, 'test-file')) as f:
+            self.assertEqual(f.read(), 'conflicting-upstream\n',
+                             "re-cloned layer must contain the upstream content after conflict backup")
+        del os.environ['BBPATH']