Files
LumaWeb/tests/tooling/test_git_commits.py
wt 522311022f
Foundation checks / foundation (push) Successful in 2m48s
added git commit after task
2026-10-14 18:06:35 +07:00

135 lines
7.3 KiB
Python

"""Real isolated Git repositories, fake agents; no user checkout commits."""
import json
from pathlib import Path
import subprocess
import unittest
from test_automation import RunnerTests, RUN_MODULE
class GitCommitTests(RunnerTests):
def git(self, *args):
return subprocess.check_output(['git', '-C', str(self.root), *args], text=True).strip()
def setUp(self):
super().setUp()
(self.root / '.gitignore').write_text('state/runs/\n.runner.lock/\n.state.lock/\n__pycache__/\n')
fake = self.root / 'fake_agent.py'
text = fake.read_text()
text = text.replace("if action=='review':t.mutate", """messages=r/'state/commit_messages';messages.mkdir(exist_ok=True)
(messages/(agent+'.txt')).write_text(('Review restored input and record acceptance' if action=='review' else 'Restore required input within assigned scope')+'\\n\\nChanges: Record the bounded fixture result and handoff.\\nValidation: Isolated positive and negative fixture checks passed.\\n')
if action=='claim':
source=r/('src/fix.txt' if id.startswith('B') else 'src/'+str(int(id.split('-')[-1]))+'.txt');source.parent.mkdir(exist_ok=True);source.write_text('verified task '+id)
if action=='review':t.mutate""")
# Remediation IDs use the actual parent-derived naming convention.
text = text.replace("id.startswith('B')", "id not in {'S00-001','S00-002','S00-003'}")
fake.write_text(text)
self.tracker.render()
self.git('init', '-b', 'main')
self.git('config', 'user.name', 'Fixture Agent')
self.git('config', 'user.email', 'fixture@example.invalid')
self.git('config', 'commit.gpgsign', 'false')
self.git('add', '.')
self.git('commit', '-m', 'Initialize isolated fixture')
self.config['git_commits'] = True
def test_chain_commits_each_transition_with_task_and_actual_message(self):
result = RUN_MODULE.Runner(self.root, self.config).run()
self.assertEqual(result['stop'], 'complete', result)
self.assertEqual(self.git('rev-list', '--count', 'HEAD'), '10')
self.assertEqual(self.git('status', '--porcelain'), '')
log = self.git('log', '--format=%B')
self.assertIn('S00-001: Restore required input within assigned scope', log)
self.assertIn('S00-001: Review restored input and record acceptance', log)
self.assertIn('Record blocked task and remediation handoff', log)
self.assertIn('Validation: Isolated positive and negative fixture checks passed.', log)
self.assertEqual(result['last_commit'], self.git('rev-parse', 'HEAD'))
commits = self.git('log', '--format=%H', '--grep=^S00-002: Restore').splitlines()
files = self.git('show', '--format=', '--name-only', commits[0])
self.assertIn('src/2.txt', files)
self.assertIn('state/tasks.json', files)
self.assertNotIn('output.log', files)
def test_dirty_existing_file_is_not_claimed_or_committed(self):
before = self.tracker.state()
(self.root / 'unrelated.txt').write_text('user work')
with self.assertRaisesRegex(ValueError, 'must be clean'):
RUN_MODULE.Runner(self.root, self.config).run()
self.assertEqual(before, self.tracker.state())
self.assertEqual(self.git('rev-list', '--count', 'HEAD'), '1')
def test_missing_message_stops_before_next_agent_and_preserves_work(self):
fake = self.root / 'fake_agent.py'
fake.write_text(fake.read_text().replace("(messages/(agent+'.txt')).write_text", "(messages/'wrong-name.txt').write_text"))
self.git('add', 'fake_agent.py')
self.git('commit', '-m', 'Simulate missing task message')
result = RUN_MODULE.Runner(self.root, self.config).run()
self.assertEqual(result['stop'], 'git_error')
self.assertEqual(result['launched'], 2) # Blocker checkpoint, then failed resolver submit.
self.assertIn('Missing task commit message', result['commit_error'])
self.assertTrue(self.git('status', '--porcelain'))
def test_rejected_git_hook_stops_without_starting_another_agent(self):
hook = self.root / '.git/hooks/pre-commit'
hook.write_text('#!/bin/sh\nexit 1\n')
hook.chmod(0o755)
result = RUN_MODULE.Runner(self.root, self.config).run()
self.assertEqual(result['stop'], 'git_error')
self.assertEqual(result['launched'], 1)
self.assertIn('Git commit failed', result['commit_error'])
self.assertEqual(self.git('rev-list', '--count', 'HEAD'), '1')
self.assertTrue(self.git('diff', '--cached', '--name-only'))
def test_changed_head_is_not_accepted_as_coordinator_commit(self):
committer = RUN_MODULE.Runner(self.root, self.config).commits
head = committer.preflight()
self.git('commit', '--allow-empty', '-m', 'Unexpected agent commit')
with self.assertRaisesRegex(ValueError, 'changed Git HEAD'):
committer.commit({'id': 'S00-001'}, 'agent', {}, {}, head, 'Fixture')
def test_nested_project_rejects_edits_to_parent_files(self):
project = self.root / 'nested'
project.mkdir()
committer = RUN_MODULE.TaskCommits(project)
head = committer.preflight()
(self.root / 'parent.txt').write_text('unrelated')
with self.assertRaisesRegex(ValueError, 'outside project root'):
committer.commit({'id': 'S00-001'}, 'agent', {}, {}, head, 'Fixture')
self.assertEqual(self.git('diff', '--cached', '--name-only'), '')
def test_nested_project_commits_literal_paths_and_deletions(self):
project = self.root / 'nested'
project.mkdir()
(project / '.gitignore').write_text('state/runs/\n')
removed = project / 'old.txt'
removed.write_text('old')
self.git('add', 'nested')
self.git('commit', '-m', 'Initialize nested fixture')
committer = RUN_MODULE.TaskCommits(project)
head = committer.preflight()
removed.unlink()
(project / 'file [1].txt').write_text('bounded output')
draft = project / 'state/commit_messages/agent.txt'
draft.parent.mkdir(parents=True)
draft.write_text('Replace obsolete fixture with literal named output\n\nChanges: Replace the obsolete fixture file.\nValidation: Checked deletion and literal path staging.\n')
# A nested foundation ships its own project-relative ignore rules.
commit = committer.commit({'id': 'S00-001', 'role': 'front', 'action': 'claim'},
'agent', {'status': 'review'}, {}, head, 'Fixture')
files = self.git('show', '--format=', '--name-only', commit)
self.assertIn('nested/file [1].txt', files)
self.assertIn('nested/old.txt', files)
self.assertFalse(removed.exists())
self.assertEqual(self.git('status', '--porcelain'), '')
def test_commit_message_rejects_empty_or_unstructured_completion(self):
committer = RUN_MODULE.TaskCommits(self.root)
message = self.root / 'state/commit_messages/agent.txt'
message.parent.mkdir(parents=True)
message.write_text('Task done\n')
with self.assertRaisesRegex(ValueError, 'concrete English subject'):
committer.message({'id': 'S00-001'}, 'agent', {'status': 'review'}, {})
def load_tests(loader, tests, pattern):
return unittest.TestSuite(GitCommitTests(name) for name in GitCommitTests.__dict__ if name.startswith('test_'))