Viewing files at commit 5717cc9fc7edCommit detailsView latest

suites/robustness/robustness/fixtures.py 174 lines · 6.9 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 = "2"
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
            meta["pre_head"] = g.metadata("head")
96
        with OakGraph(server.db()) as g:
97
            meta["pre_server_heads"] = g.branch_heads()
98
    finally:
99
        server.stop()
100
    if other.exists():
101
        shutil.rmtree(other)
102
    shutil.rmtree(base / "home", ignore_errors=True)
103
    fixture = Fixture("oak_installed", "oak", op, root, port, branch, meta)
104
    fixture.save()
105
    return fixture
106
107
108
def build_git(git_bin: str, op: str, root: Path, port: Optional[int] = None) -> Fixture:
109
    if root.exists():
110
        shutil.rmtree(root)
111
    base = root / "pristine"
112
    client, other, srv = base / "client", base / "other", base / "srv"
113
    client.mkdir(parents=True)
114
    srv.mkdir(parents=True)
115
    adapter = GitAdapter(git_bin, base / "home")
116
    g = git_bin
117
    adapter.must([g, "init", "-q", "--bare", "-b", "main", "remote.git"], srv)
118
    adapter.must([g, "config", "http.receivepack", "true"], srv / "remote.git")
119
    adapter.must([g, "init", "-q", "-b", "main", "."], client)
120
    payload(client, "base")
121
    adapter.must([g, "add", "-A"], client)
122
    adapter.must([g, "commit", "-q", "-m", "base"], client)
123
    adapter.must([g, "remote", "add", "origin", "../srv/remote.git"], client)
124
    adapter.must([g, "push", "-q", "-u", "origin", "main"], client)
125
    branch = "main"
126
    meta: dict = {"url": "srv/remote.git", "repo_id": "remote.git"}
127
    if op == "commit":
128
        payload(client, "new")
129
        payload(client, "added", count=20, start=200, prefix="n")
130
    elif op == "push":
131
        payload(client, "new")
132
        adapter.must([g, "commit", "-q", "-am", "new"], client)
133
    elif op in ("pull_ff", "pull_diverged"):
134
        adapter.must([g, "clone", "-q", "srv/remote.git", "other"], base)
135
        payload(other, "theirs", count=100)
136
        payload(other, "theirs-new", count=50, start=300, prefix="t")
137
        adapter.must([g, "add", "-A"], other)
138
        adapter.must([g, "commit", "-q", "-m", "theirs"], other)
139
        adapter.must([g, "push", "-q", "origin", "main"], other)
140
        if op == "pull_diverged":
141
            (client / "local.txt").write_text("local unpushed work\n")
142
            payload(client, "mine", count=50, start=150)
143
            adapter.must([g, "add", "-A"], client)
144
            adapter.must([g, "commit", "-q", "-m", "mine"], client)
145
    elif op in ("switch", "merge_local"):
146
        adapter.must([g, "switch", "-q", "-c", OTHER_BRANCH], client)
147
        if op == "switch":
148
            payload(client, "side")
149
        else:
150
            payload(client, "side", count=100, start=100)
151
        payload(client, "side-new", count=50, start=400, prefix="s")
152
        adapter.must([g, "add", "-A"], client)
153
        adapter.must([g, "commit", "-q", "-m", "side"], client)
154
        adapter.must([g, "switch", "-q", "main"], client)
155
        if op == "merge_local":
156
            payload(client, "mainside", count=50)
157
            adapter.must([g, "commit", "-q", "-am", "mainside"], client)
158
        meta["target"] = OTHER_BRANCH
159
    elif op == "clone":
160
        pass
161
    else:
162
        raise KeyError(op)
163
    oracle = adapter.oracle
164
    client_refs = oracle.refs(client)
165
    meta["pre_local_heads"] = sorted(set(client_refs.values()))
166
    meta["pre_head"] = client_refs.get("HEAD")
167
    meta["pre_server_heads"] = {k.removeprefix("refs/heads/"): v for k, v in oracle.refs(srv / "remote.git").items()
168
                                if k.startswith("refs/heads/")}
169
    if other.exists():
170
        shutil.rmtree(other)
171
    shutil.rmtree(base / "home", ignore_errors=True)
172
    fixture = Fixture("git", "git", op, root, port, branch, meta)
173
    fixture.save()
174
    return fixture