Skip to content

Commit 4b4cc0b

Browse files
committed
Fix executable caching bug
- Make index non-unique to resolve FileExists errors of gridFs. - Rename binary_cache to executable_cache for consistency. - Ruff changed double quotes to single quotes in some files. - Remove REPORT_LEAK command. - Fix in simulation.
1 parent 51c099b commit 4b4cc0b

File tree

10 files changed

+169
-166
lines changed

10 files changed

+169
-166
lines changed

bughog/configuration.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,17 @@ def get_database_params() -> DatabaseParameters:
5353

5454
required_database_params = ['BCI_MONGO_HOST', 'BCI_MONGO_USERNAME', 'BCI_MONGO_DATABASE', 'BCI_MONGO_PASSWORD']
5555
missing_database_params = [param for param in required_database_params if os.getenv(param) in ['', None]]
56-
binary_cache_limit = int(os.getenv('BCI_BINARY_CACHE_LIMIT', 0))
56+
executable_cache_limit = int(os.getenv('BCI_EXECUTABLE_CACHE_LIMIT', 0))
5757
if missing_database_params:
5858
logger.info(f'Could not find database parameters {missing_database_params}, using database container...')
59-
Global.__database_params = container.run(binary_cache_limit)
59+
Global.__database_params = container.run(executable_cache_limit)
6060
else:
6161
Global.__database_params = DatabaseParameters(
6262
os.getenv('BCI_MONGO_HOST'),
6363
os.getenv('BCI_MONGO_USERNAME'),
6464
os.getenv('BCI_MONGO_PASSWORD'),
6565
os.getenv('BCI_MONGO_DATABASE'),
66-
binary_cache_limit,
66+
executable_cache_limit,
6767
)
6868
logger.info(f"Found database environment variables '{Global.__database_params}'")
6969
return Global.__database_params

bughog/database/mongo/container.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
DEFAULT_HOST = 'bh_db'
2121

2222

23-
def run(binary_cache_limit: int) -> DatabaseParameters:
23+
def run(executable_cache_limit: int) -> DatabaseParameters:
2424
docker_client = docker.from_env()
2525
try:
2626
mongo_container = docker_client.containers.get(DEFAULT_HOST)
@@ -33,7 +33,7 @@ def run(binary_cache_limit: int) -> DatabaseParameters:
3333
LOGGER.debug('MongoDB container not found, creating a new one...')
3434
__create_new_container(DEFAULT_USER, DEFAULT_PW, DEFAULT_DB_NAME, DEFAULT_HOST)
3535
LOGGER.debug('MongoDB container has started!')
36-
return DatabaseParameters(DEFAULT_HOST, DEFAULT_USER, DEFAULT_PW, DEFAULT_DB_NAME, binary_cache_limit)
36+
return DatabaseParameters(DEFAULT_HOST, DEFAULT_USER, DEFAULT_PW, DEFAULT_DB_NAME, executable_cache_limit)
3737

3838

3939
def stop():

bughog/database/mongo/executable_cache.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,12 @@ def store_file(file_path: str) -> None:
129129
),
130130
exc_info=futures_with_exception[0].exception(),
131131
)
132-
ExecutableCache.__remove_commit_executable_files(state_name)
132+
ExecutableCache.__remove_commit_executable_files(subject_config.subject_type, subject_config.subject_name, state_name)
133133
logger.debug(f'Removed possibly incomplete cached executable files for {state_name}.')
134134
else:
135135
elapsed_time = time.time() - start_time
136136
logger.debug(f'Stored executable in {elapsed_time:.2f}s')
137137

138-
@staticmethod
139-
def remove_executable_files(state_name) -> None:
140-
ExecutableCache.__remove_commit_executable_files(state_name)
141-
142138
@staticmethod
143139
def __count_cached_executables(state_type: Optional[str] = None) -> int:
144140
"""
@@ -167,16 +163,24 @@ def __remove_least_used_commit_executable_files() -> None:
167163
)
168164
for state_doc in grid_cursor:
169165
state_name = state_doc['state_name']
170-
ExecutableCache.__remove_commit_executable_files(state_name)
166+
subject_type = state_doc['subject_type']
167+
subject_name = state_doc['subject_name']
168+
ExecutableCache.__remove_commit_executable_files(subject_type, subject_name, state_name)
171169
break
172170

173171
@staticmethod
174-
def __remove_commit_executable_files(state_name: str) -> None:
172+
def __remove_commit_executable_files(subject_type: str, subject_name: str, state_name: str) -> None:
175173
"""
176174
Removes the executable files associated with the parameters.
177175
"""
178176
fs = MongoDB().gridfs
179177
files_collection = MongoDB().get_collection('fs.files')
180178

181-
for grid_doc in files_collection.find({'state_name': state_name}):
179+
query = {
180+
'file_type': 'executable',
181+
'state_name': state_name,
182+
'subject_type': subject_type,
183+
'subject_name': subject_name,
184+
}
185+
for grid_doc in files_collection.find(query):
182186
fs.delete(grid_doc['_id'])

bughog/database/mongo/mongodb.py

Lines changed: 117 additions & 116 deletions
Large diffs are not rendered by default.

bughog/parameters.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class EvaluationParameters:
2222

2323
def serialize(self) -> str:
2424
pickled_bytes = pickle.dumps(self, pickle.HIGHEST_PROTOCOL)
25-
return base64.b64encode(pickled_bytes).decode("ascii")
25+
return base64.b64encode(pickled_bytes).decode('ascii')
2626

2727
@staticmethod
2828
def deserialize(pickled_str: str) -> EvaluationParameters:
@@ -53,7 +53,7 @@ def to_dict(self) -> dict:
5353

5454
@staticmethod
5555
def from_dict(data: dict) -> SubjectConfiguration:
56-
return SubjectConfiguration(data["subject_type"], data["subject_name"], data["subject_setting"], data["cli_options"], data["extensions"])
56+
return SubjectConfiguration(data['subject_type'], data['subject_name'], data['subject_setting'], data['cli_options'], data['extensions'])
5757

5858

5959
@dataclass(frozen=True)
@@ -70,7 +70,7 @@ def __post_init__(self):
7070
elif self.commit_nb_range:
7171
assert self.commit_nb_range[0] <= self.commit_nb_range[1]
7272
else:
73-
raise AttributeError("Evaluation ranges require either major versions or commit numbers")
73+
raise AttributeError('Evaluation ranges require either major versions or commit numbers')
7474

7575

7676
@dataclass(frozen=True)
@@ -86,26 +86,26 @@ class DatabaseParameters:
8686
username: str
8787
password: str
8888
database_name: str
89-
binary_cache_limit: int
89+
executable_cache_limit: int
9090

9191
def to_dict(self) -> dict:
9292
return asdict(self)
9393

9494
@staticmethod
9595
def from_dict(data: dict) -> DatabaseParameters:
9696
return DatabaseParameters(
97-
data["host"],
98-
data["username"],
99-
data["password"],
100-
data["database_name"],
101-
data["binary_cache_limit"],
97+
data['host'],
98+
data['username'],
99+
data['password'],
100+
data['database_name'],
101+
data['executable_cache_limit'],
102102
)
103103

104104
def __str__(self) -> str:
105-
return f"{self.username}@{self.host}:27017/{self.database_name}"
105+
return f'{self.username}@{self.host}:27017/{self.database_name}'
106106

107107
def __repr__(self) -> str:
108-
return f"{self.username}@{self.host}:27017/{self.database_name}"
108+
return f'{self.username}@{self.host}:27017/{self.database_name}'
109109

110110

111111
@dataclass(frozen=True)
@@ -146,29 +146,28 @@ class PlotParameters(EvaluationParameters):
146146
# )
147147

148148

149-
150149
@staticmethod
151150
def evaluation_factory(kwargs: dict, database_params: DatabaseParameters, only_to_plot=False) -> list[EvaluationParameters]:
152-
experiments = set(x for x in kwargs.get("experiments", []) + [kwargs.get("experiment_to_plot")] if x is not None)
151+
experiments = set(x for x in kwargs.get('experiments', []) + [kwargs.get('experiment_to_plot')] if x is not None)
153152
if len(experiments) == 0:
154153
raise MissingParametersException()
155154

156155
subject_configuration = SubjectConfiguration.from_dict(kwargs)
157156
sequence_configuration = SequenceConfiguration(
158-
int(kwargs.get("nb_of_containers", 1)),
159-
int(kwargs.get("sequence_limit", 50)),
160-
kwargs.get("search_strategy"),
157+
int(kwargs.get('nb_of_containers', 1)),
158+
int(kwargs.get('sequence_limit', 50)),
159+
kwargs.get('search_strategy'),
161160
)
162161
evaluation_params_list = []
163162
for experiment in sorted(experiments):
164163
if only_to_plot and experiment != kwargs.get('experiment_to_plot'):
165164
continue
166165
evaluation_range = EvaluationRange(
167-
kwargs["project_name"],
166+
kwargs['project_name'],
168167
experiment,
169168
__get_version_range(kwargs),
170169
__get_commit_nb_range(kwargs),
171-
kwargs.get("only_release_commits", False),
170+
kwargs.get('only_release_commits', False),
172171
)
173172
evaluation_params = EvaluationParameters(
174173
subject_configuration,
@@ -182,11 +181,11 @@ def evaluation_factory(kwargs: dict, database_params: DatabaseParameters, only_t
182181

183182
@staticmethod
184183
def __get_cookie_name(form_data: dict[str, str]) -> str | None:
185-
if form_data["check_for"] == "request":
184+
if form_data['check_for'] == 'request':
186185
return None
187-
if "cookie_name" in form_data:
188-
return form_data["cookie_name"]
189-
return "generic"
186+
if 'cookie_name' in form_data:
187+
return form_data['cookie_name']
188+
return 'generic'
190189

191190

192191
@staticmethod
@@ -199,8 +198,8 @@ def __get_version_range(form_data: dict[str, str]) -> tuple[int, int] | None:
199198

200199
@staticmethod
201200
def __get_commit_nb_range(form_data: dict[str, str]) -> tuple[int, int] | None:
202-
lower_rev_number = form_data.get("lower_commit_nb", None)
203-
upper_rev_number = form_data.get("upper_commit_nb", None)
201+
lower_rev_number = form_data.get('lower_commit_nb', None)
202+
upper_rev_number = form_data.get('upper_commit_nb', None)
204203
lower_rev_number = int(lower_rev_number) if lower_rev_number else None
205204
upper_rev_number = int(upper_rev_number) if upper_rev_number else None
206205
if lower_rev_number is None or upper_rev_number is None:

bughog/subject/web_browser/interaction/simulation.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
from bughog.evaluation.file_structure import Folder
88
from bughog.parameters import EvaluationParameters
9-
from bughog.subject.executable import Executable
109
from bughog.subject.simulation import Simulation
10+
from bughog.subject.web_browser.executable import BrowserExecutable
1111

1212
# TODO: all pyautogui are imported inside functions because the import needs DISPLAY var, while not all containers need and have that.
1313

1414
class BrowserSimulation(Simulation):
15-
def __init__(self, executable: Executable, folder: Folder, params: EvaluationParameters):
15+
def __init__(self, executable: BrowserExecutable, folder: Folder, params: EvaluationParameters):
1616
import pyautogui as gui
1717
super().__init__(executable, folder, params)
1818
disp = Display(visible=True, size=(1920, 1080), backend='xvfb', use_xauth=True)
@@ -36,7 +36,7 @@ def supported_commands(self) -> list[str]:
3636
'hotkey',
3737
'sleep',
3838
'screenshot',
39-
'report_leak',
39+
'reproduced',
4040
'assert_file_contains',
4141
'open_file',
4242
'open_console',
@@ -111,7 +111,7 @@ def screenshot(self, filename: str):
111111
file_path = os.path.join('/app/logs/screenshots/', f'{project_name}-{experiment_name}-{self.executable.state.name}-{executable_name}.jpg')
112112
gui.screenshot(file_path)
113113

114-
def report_leak(self):
114+
def reproduced(self):
115115
self.navigate('https://a.test/report/?bughog_reproduced=ok')
116116

117117
def assert_file_contains(self, filename: str, content: str):
@@ -128,7 +128,7 @@ def open_file(self, filename: str):
128128
self.navigate(f'file:///root/Downloads/{filename}')
129129

130130
def open_console(self):
131-
self.hotkey(*self.executable.get_open_console_hotkey())
131+
self.hotkey(*self.executable.open_console_hotkey)
132132
self.sleep('1.5')
133133

134134

bughog/web/blueprints/experiments.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,15 @@ def python_evaluation(project: str, experiment: str, file_name: str):
143143
sys.modules[module_name] = module
144144
spec.loader.exec_module(module)
145145

146-
def report_leak() -> None:
146+
def reproduced() -> None:
147147
remote_ip = request.headers.get("X-Real-IP")
148148
response_data = {
149-
"url": url_for("experiments.report_endpoint", leak=experiment),
149+
"url": url_for("experiments.report_endpoint", bughog_reproduced='OK'),
150150
"method": request.method,
151151
"headers": dict(request.headers),
152152
"content": request.data.decode("utf-8"),
153153
}
154154

155155
requests.post(f"http://{remote_ip}:5001/report/", json=response_data, timeout=5)
156156

157-
return module.main(request, report_leak)
157+
return module.main(request, reproduced)

bughog/web/vue/src/interaction_script_mode.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const KEYWORDS = "NAVIGATE|NEW_TAB|CLICK_POSITION|CLICK|WRITE|PRESS|HOLD|RELEASE|HOTKEY|SLEEP|SCREENSHOT|REPORT_LEAK|ASSERT_FILE_CONTAINS|OPEN_FILE|OPEN_CONSOLE";
1+
const KEYWORDS = "NAVIGATE|NEW_TAB|CLICK_POSITION|CLICK|WRITE|PRESS|HOLD|RELEASE|HOTKEY|SLEEP|SCREENSHOT|ASSERT_FILE_CONTAINS|OPEN_FILE|OPEN_CONSOLE";
22

33
ace.define("ace/mode/interaction_script_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module){"use strict";
44
const oop = require("../lib/oop");
@@ -44,4 +44,4 @@ const getMode = () => new Promise((resolve) => ace.require(["ace/mode/interactio
4444

4545
export {
4646
getMode,
47-
};
47+
};

config/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ GITHUB_TOKEN=
66

77
# Cache parameters
88
# All binaries will be cached in the active MongoDB (either a local Docker container, or the one configured below).
9-
BCI_BINARY_CACHE_LIMIT=
9+
BCI_EXECUTABLE_CACHE_LIMIT=
1010

1111
# Database parameters
1212
BCI_MONGO_HOST=

subject/web_browser/experiments/_default_files/script.cmd

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
# RELEASE key
1212
# HOTKEY key1 key2 ...
1313
# SLEEP seconds where seconds is a float or an int
14-
# REPORT_LEAK
1514
# ASSERT_FILE_CONTAINS file content if the downloaded file exists and contains the given content as a substring, the evaluation continues
1615
# otherwise the evaluation terminates and the exact reason is reported
1716

1817
### Debugging commands
1918
# SCREENSHOT file_name
2019
# OPEN_FILE file
21-
# OPEN_CONSOLE
20+
# OPEN_CONSOLE

0 commit comments

Comments
 (0)