Viewing files at commit f2acb751aa96Commit detailsView latest

suites/robustness/robustness/fixtures.py 171 lines · 6.8 KB · python
1
"""Deterministic per-(subject, operation) fixtures, built once and copied per trial.
2
3
Layout under ``<root>/pristine``: ``client/`` (repo under test), ``other/``
4
(second clone used to advance the remote), ``srv/`` (oak serve data dir or
5
Git bare ``remote.git``). ``meta.json`` records the pre-state every trial is
6
judged against (read with the independent readers, never the subject).
7
"""
8
9
from __future__ import annotations
10
11
import json
12
import shutil
13
from dataclasses import dataclass, field
14
from pathlib import Path
15
from typing import Optional
16
17
from .adapters import OTHER_BRANCH, REPO_ID, GitAdapter, OakAdapter, Server, payload
18
from .readers import OakGraph
19
20
FIXTURE_VERSION = "1"
21
22
23
@dataclass
24
class Fixture:
25
    subject: str
26
    kind: str
27
    op: str
28
    root: Path
29
    port: Optional[int]
30
    branch: str
31
    meta: dict = field(default_factory=dict)
32
33
    @property
34
    def pristine(self) -> Path:
35
        return self.root / "pristine"
36
37
    def save(self) -> None:
38
        (self.root / "meta.json").write_text(json.dumps(self.meta, indent=2, sort_keys=True))
39
40
41
def _oak_branch(repo: Path) -> str:
42
    with OakGraph(repo / ".oak" / "oak.db") as g:
43
        branch = g.metadata("current_branch")
44
    if not branch:
45
        raise RuntimeError("fixture: no current branch after first push")
46
    return branch
47
48
49
def build_oak(oak_bin: str, op: str, root: Path, port: int) -> Fixture:
50
    if root.exists():
51
        shutil.rmtree(root)
52
    base = root / "pristine"
53
    client, other, srv = base / "client", base / "other", base / "srv"
54
    for d in (client, srv):
55
        d.mkdir(parents=True)
56
    adapter = OakAdapter(oak_bin, base / "home")
57
    server = Server(oak_bin, srv, port, root / "fixture-serve.log").start()
58
    try:
59
        url = server.url
60
        adapter.must([oak_bin, "init", "."], client)
61
        payload(client, "base")
62
        adapter.must([oak_bin, "commit"], client)
63
        adapter.must([oak_bin, "push", "--repo", REPO_ID, "-r", url], client)
64
        branch = _oak_branch(client)
65
        meta: dict = {"url": url, "repo_id": REPO_ID}
66
        if op == "commit":
67
            payload(client, "new")
68
            payload(client, "added", count=20, start=200, prefix="n")
69
        elif op == "push":
70
            payload(client, "new")
71
            adapter.must([oak_bin, "commit"], client)
72
        elif op in ("pull_ff", "pull_diverged"):
73
            adapter.must([oak_bin, "clone", REPO_ID, str(other), "-r", url, "--branch", branch], base)
74
            payload(other, "theirs", count=100)
75
            payload(other, "theirs-new", count=50, start=300, prefix="t")
76
            adapter.must([oak_bin, "commit"], other)
77
            adapter.must([oak_bin, "push"], other)
78
            if op == "pull_diverged":
79
                (client / "local.txt").write_text("local unpushed work\n")
80
                payload(client, "mine", count=50, start=150)
81
                adapter.must([oak_bin, "commit"], client)
82
        elif op == "switch":
83
            adapter.must([oak_bin, "switch", "-c", OTHER_BRANCH], client)
84
            payload(client, "side")
85
            payload(client, "side-new", count=50, start=400, prefix="s")
86
            adapter.must([oak_bin, "commit"], client)
87
            adapter.must([oak_bin, "switch", branch], client)
88
            meta["target"] = OTHER_BRANCH
89
        elif op == "clone":
90
            pass
91
        else:
92
            raise KeyError(op)
93
        with OakGraph(client / ".oak" / "oak.db") as g:
94
            meta["pre_local_heads"] = sorted(g.ref_heads())
95
        with OakGraph(server.db()) as g:
96
            meta["pre_server_heads"] = g.branch_heads()
97
    finally:
98
        server.stop()
99
    if other.exists():
100
        shutil.rmtree(other)
101
    shutil.rmtree(base / "home", ignore_errors=True)
102
    fixture = Fixture("oak_installed", "oak", op, root, port, branch, meta)
103
    fixture.save()
104
    return fixture
105
106
107
def build_git(git_bin: str, op: str, root: Path, port: Optional[int] = None) -> Fixture:
108
    if root.exists():
109
        shutil.rmtree(root)
110
    base = root / "pristine"
111
    client, other, srv = base / "client", base / "other", base / "srv"
112
    client.mkdir(parents=True)
113
    srv.mkdir(parents=True)
114
    adapter = GitAdapter(git_bin, base / "home")
115
    g = git_bin
116
    adapter.must([g, "init", "-q", "--bare", "-b", "main", "remote.git"], srv)
117
    adapter.must([g, "config", "http.receivepack", "true"], srv / "remote.git")
118
    adapter.must([g, "init", "-q", "-b", "main", "."], client)
119
    payload(client, "base")
120
    adapter.must([g, "add", "-A"], client)
121
    adapter.must([g, "commit", "-q", "-m", "base"], client)
122
    adapter.must([g, "remote", "add", "origin", "../srv/remote.git"], client)
123
    adapter.must([g, "push", "-q", "-u", "origin", "main"], client)
124
    branch = "main"
125
    meta: dict = {"url": "srv/remote.git", "repo_id": "remote.git"}
126
    if op == "commit":
127
        payload(client, "new")
128
        payload(client, "added", count=20, start=200, prefix="n")
129
    elif op == "push":
130
        payload(client, "new")
131
        adapter.must([g, "commit", "-q", "-am", "new"], client)
132
    elif op in ("pull_ff", "pull_diverged"):
133
        adapter.must([g, "clone", "-q", "srv/remote.git", "other"], base)
134
        payload(other, "theirs", count=100)
135
        payload(other, "theirs-new", count=50, start=300, prefix="t")
136
        adapter.must([g, "add", "-A"], other)
137
        adapter.must([g, "commit", "-q", "-m", "theirs"], other)
138
        adapter.must([g, "push", "-q", "origin", "main"], other)
139
        if op == "pull_diverged":
140
            (client / "local.txt").write_text("local unpushed work\n")
141
            payload(client, "mine", count=50, start=150)
142
            adapter.must([g, "add", "-A"], client)
143
            adapter.must([g, "commit", "-q", "-m", "mine"], client)
144
    elif op in ("switch", "merge_local"):
145
        adapter.must([g, "switch", "-q", "-c", OTHER_BRANCH], client)
146
        if op == "switch":
147
            payload(client, "side")
148
        else:
149
            payload(client, "side", count=100, start=100)
150
        payload(client, "side-new", count=50, start=400, prefix="s")
151
        adapter.must([g, "add", "-A"], client)
152
        adapter.must([g, "commit", "-q", "-m", "side"], client)
153
        adapter.must([g, "switch", "-q", "main"], client)
154
        if op == "merge_local":
155
            payload(client, "mainside", count=50)
156
            adapter.must([g, "commit", "-q", "-am", "mainside"], client)
157
        meta["target"] = OTHER_BRANCH
158
    elif op == "clone":
159
        pass
160
    else:
161
        raise KeyError(op)
162
    oracle = adapter.oracle
163
    meta["pre_local_heads"] = sorted(set(oracle.refs(client).values()))
164
    meta["pre_server_heads"] = {k.removeprefix("refs/heads/"): v for k, v in oracle.refs(srv / "remote.git").items()
165
                                if k.startswith("refs/heads/")}
166
    if other.exists():
167
        shutil.rmtree(other)
168
    shutil.rmtree(base / "home", ignore_errors=True)
169
    fixture = Fixture("git", "git", op, root, port, branch, meta)
170
    fixture.save()
171
    return fixture