Skip to content

Commit 0ee354c

Browse files
GitBug-Java v1.0.0 (#14)
--------- Co-authored-by: Nfsaavedra <nuno.saavedra@tecnico.ulisboa.pt>
1 parent 58be636 commit 0ee354c

File tree

14 files changed

+834
-552
lines changed

14 files changed

+834
-552
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Gitbug-Java
2+
output/
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[cod]
@@ -158,3 +161,10 @@ cython_debug/
158161
# and can be added to the global gitignore or merged into this file. For a more nuclear
159162
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160163
#.idea/
164+
165+
166+
# GitBug-Java
167+
act-cache/
168+
data/
169+
!data/bugs/
170+
report/

bin/act

379 KB
Binary file not shown.

data/bugs/giraud-reasonml-idea-plugin.json

Lines changed: 0 additions & 1 deletion
Large diffs are not rendered by default.

gitbug-java

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import sys, grp
5+
import fire
6+
import json
7+
import tqdm
8+
import shutil
9+
import tarfile
10+
import logging
11+
import zipfile
12+
import tempfile
13+
import requests
14+
15+
from pathlib import Path
16+
from gitbug.project import Project
17+
from gitbugactions.docker.client import DockerClient
18+
from gitbug.bug import Bug
19+
from gitbug.bug import get_project_root
20+
from typing import Optional
21+
22+
23+
class GitBugJavaCli(object):
24+
"""GitBug-Java CLI"""
25+
26+
def __init__(self, verbose: bool = False):
27+
self.__init_logging(verbose)
28+
self.__projects = {}
29+
30+
# Load the GitBug-Java dataset
31+
for project_file in Path(get_project_root(), "data", "bugs").glob("*.json"):
32+
with project_file.open("r") as f:
33+
pid = project_file.stem
34+
project = Project(pid)
35+
for line in f.readlines():
36+
bug = json.loads(line)
37+
project.add_bug(Bug(bug))
38+
self.__projects[pid] = project
39+
40+
# Sort the projects by pid
41+
self.__projects = dict(sorted(self.__projects.items(), key=lambda x: x[0]))
42+
43+
def __init_logging(self, verbose: bool = False):
44+
level = logging.DEBUG if verbose else logging.ERROR
45+
logging.basicConfig(format="[%(asctime)s] %(message)s", level=level)
46+
47+
def __setup_base_image(self):
48+
base_image = f"nunosaavedra/gitbug-actions:setup"
49+
runner_image = f"gitbug-java:base"
50+
51+
client = DockerClient.getInstance()
52+
# Return if image already exists
53+
if len(client.images.list(name=runner_image)) > 0:
54+
return
55+
56+
tmp_dir = tempfile.mkdtemp()
57+
Path(tmp_dir).mkdir(parents=True, exist_ok=True)
58+
dockerfile_path = Path(tmp_dir, "Dockerfile")
59+
with dockerfile_path.open("w") as f:
60+
dockerfile = f"FROM {base_image}\n"
61+
# HACK: We set runneradmin to an arbitrarily large uid to avoid conflicts with the host's
62+
dockerfile += f"RUN sudo usermod -u 4000000 runneradmin\n"
63+
dockerfile += f"RUN sudo groupadd -o -g {os.getgid()} {grp.getgrgid(os.getgid()).gr_name}\n"
64+
dockerfile += f"RUN sudo usermod -G {os.getgid()} runner\n"
65+
dockerfile += f"RUN sudo usermod -o -u {os.getuid()} runner\n"
66+
f.write(dockerfile)
67+
68+
client.images.build(path=tmp_dir, tag=runner_image, forcerm=True)
69+
shutil.rmtree(tmp_dir, ignore_errors=True)
70+
71+
def __download(self, url: str, filename: str):
72+
with open(filename, "wb") as f:
73+
with requests.get(url, stream=True) as r:
74+
r.raise_for_status()
75+
total = int(r.headers.get("content-length", 0))
76+
77+
# tqdm has many interesting parameters. Feel free to experiment!
78+
tqdm_params = {
79+
"desc": url,
80+
"total": total,
81+
"miniters": 1,
82+
"unit": "B",
83+
"unit_scale": True,
84+
"unit_divisor": 1024,
85+
}
86+
with tqdm.tqdm(**tqdm_params) as pb:
87+
for chunk in r.iter_content(chunk_size=8192):
88+
pb.update(len(chunk))
89+
f.write(chunk)
90+
91+
def __setup_exports(self, name: str, download_url: str):
92+
gitbug = Path(get_project_root(), "data", f"{name}.tar.gz")
93+
if not gitbug.exists():
94+
self.__download(download_url, gitbug)
95+
# Extract the dataset
96+
with tarfile.open(gitbug) as tar:
97+
tar.extractall("data")
98+
99+
data_dir = Path(get_project_root(), "data")
100+
source = os.path.join(data_dir, f"{name}")
101+
shutil.copytree(source, data_dir, dirs_exist_ok=True)
102+
shutil.rmtree(source)
103+
os.remove(gitbug)
104+
105+
def __setup_act_cache(self, name: str, download_url: str):
106+
# Download the zip
107+
act_cache = Path(f"{name}.zip")
108+
if not act_cache.exists():
109+
self.__download(download_url, act_cache)
110+
# Extract the zip
111+
Path("./act-cache").mkdir(parents=True, exist_ok=True)
112+
with zipfile.ZipFile(f"{name}.zip", "r") as zip_ref:
113+
zip_ref.extractall("./act-cache")
114+
os.remove(f"{name}.zip")
115+
116+
def pids(self):
117+
"""
118+
List all available project ids in GitBug-Java
119+
"""
120+
for project in self.__projects:
121+
print(project)
122+
123+
def bids(self, pid: str = None):
124+
"""
125+
List all available bug ids in GitBug-Java (for a given project id if specified)
126+
"""
127+
for ppid in self.__projects:
128+
if pid is None or ppid == pid:
129+
for bug in self.__projects[ppid].get_bugs():
130+
print(bug.bid)
131+
132+
def checkout(self, bid: str, workdir: str, fixed: bool = False):
133+
"""
134+
Checkout a specific bug in GitBug-Java
135+
"""
136+
# Split bid on the last occurence of "-"
137+
pid, _ = bid.rsplit("-", 1)
138+
139+
# Check the pid and bid exist
140+
if pid not in self.__projects:
141+
raise ValueError(f"Unknown project id {pid}")
142+
143+
project = self.__projects[pid]
144+
bug = project.get_bug(bid)
145+
if bug is None:
146+
raise ValueError(f"Unknown bug id {bid} for project {pid}")
147+
148+
# Create the workdir if it does not exist
149+
os.makedirs(workdir, exist_ok=True)
150+
151+
# Make sure the workdir is empty
152+
if os.listdir(workdir):
153+
raise ValueError(f"Workdir {workdir} is not empty")
154+
155+
# Checkout the bug
156+
bug.checkout(workdir, fixed=fixed)
157+
158+
def run(
159+
self,
160+
workdir: str,
161+
output: str = "report",
162+
act_cache_dir: Optional[str] = "./act-cache",
163+
timeout: int = 0,
164+
) -> bool:
165+
"""
166+
Run the bug checked-out in workdir
167+
"""
168+
if not Path(output).exists():
169+
os.makedirs(output)
170+
171+
# Read the bug info from the workdir
172+
with Path(workdir, "gitbug.json").open("r") as f:
173+
bug_info = json.load(f)
174+
bug = Bug(bug_info)
175+
176+
# Run the bug
177+
return bug.run(workdir, output, act_cache_dir=act_cache_dir, timeout=timeout)
178+
179+
def setup(self):
180+
"""
181+
Setup the GitBug-Java dataset
182+
"""
183+
if not os.path.exists(os.path.join(get_project_root(), "data")):
184+
os.makedirs(os.path.join(get_project_root(), "data"))
185+
self.__setup_base_image()
186+
# TODO: check if exports are already downloaded
187+
self.__setup_exports(
188+
"gitbug-java_offline_environments_1",
189+
"https://zenodo.org/records/10578602/files/gitbug-java_offline_environments_1.tar.gz?download=1",
190+
)
191+
self.__setup_exports(
192+
"gitbug-java_offline_environments_2",
193+
"https://zenodo.org/records/10578617/files/gitbug-java_offline_environments_2.tar.gz?download=1",
194+
)
195+
self.__setup_act_cache(
196+
"act-cache",
197+
"https://zenodo.org/records/10592626/files/act-cache.zip?download=1",
198+
)
199+
200+
201+
def main():
202+
fire.Fire(GitBugJavaCli)
203+
204+
205+
if __name__ == "__main__":
206+
sys.exit(main())
File renamed without changes.

0 commit comments

Comments
 (0)