bond-spy 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/bond_reconcile.py +224 -134
- data/bond.gemspec +2 -1
- data/lib/bond.rb +53 -35
- data/lib/bond/targetable.rb +9 -3
- data/lib/bond/version.rb +1 -1
- data/spec/bond_spec.rb +16 -0
- data/spec/bond_targetable_spec.rb +13 -0
- data/spec/test_observations/bond_spec/Bond_with_agents_should_call_doers_before_returning_result.json +4 -4
- data/spec/test_observations/bond_spec/Bond_with_agents_should_call_the_function_passed_as_result_if_it_is_callable.json +9 -9
- data/spec/test_observations/bond_spec/Bond_with_agents_should_correctly_call_a_single_doer_if_filter_criteria_are_met.json +4 -4
- data/spec/test_observations/bond_spec/Bond_with_agents_should_correctly_call_multiple_doers.json +5 -5
- data/spec/test_observations/bond_spec/Bond_with_agents_should_not_call_doers_of_overriden_agents.json +2 -2
- data/spec/test_observations/bond_spec/Bond_with_agents_should_skip_saving_observations_when_specified.json +18 -0
- data/spec/test_observations/bond_spec/Bond_with_agents_should_throw_an_exception_if_specified_by_agent.json +3 -3
- data/spec/test_observations/bond_spec/Bond_with_agents_should_throw_the_result_of_the_value_passed_to_exception_if_callable.json +4 -4
- data/spec/test_observations/bond_spec/Bond_with_agents_should_work_with_multiple_agents_for_different_spy_points.json +9 -9
- data/spec/test_observations/bond_spec/Bond_with_agents_with_filters_should_respect_combinations_of_filters.json +19 -19
- data/spec/test_observations/bond_spec/Bond_with_agents_with_filters_should_respect_function_filters.json +6 -6
- data/spec/test_observations/bond_spec/Bond_with_agents_with_filters_should_respect_single_key_value_filters_of_all_types.json +51 -51
- data/spec/test_observations/bond_spec/Bond_without_any_agents_should_correctly_log_nested_hashes_and_arrays_with_hash_sorting.json +22 -22
- data/spec/test_observations/bond_spec/Bond_without_any_agents_should_correctly_log_some_normal_arguments_with_a_spy_point_name.json +6 -6
- data/spec/test_observations/bond_spec/Bond_without_any_agents_should_correctly_log_some_normal_arguments_without_a_spy_point_name.json +4 -4
- data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_continues_to_the_method_when_agent_result_continue_is_returned.json +4 -4
- data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_continues_to_the_method_when_agent_result_none_is_returned.json +6 -6
- data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_spies_private_methods.json +2 -2
- data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_spies_protected_methods.json +2 -2
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_class_method.json +3 -3
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_normal_method.json +3 -3
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_all_optional_keyword_arguments.json +4 -4
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_variable_keyword_arguments.json +12 -12
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_ignores_excluded_keys.json +4 -4
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_mocks_when_one_is_specified.json +4 -4
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_respects_mock_only.json +17 -0
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_modules_correctly_spies_on_included_module_methods.json +2 -2
- data/spec/test_observations/bond_targetable_spec/BondTargetable_with_modules_correctly_spies_on_module_methods.json +3 -3
- data/tutorials/binary_search_tree/bst_spec.rb +1 -5
- data/tutorials/binary_search_tree/run_tests.sh +1 -1
- data/tutorials/heat_watcher/heat_watcher.rb +2 -2
- data/tutorials/heat_watcher/heat_watcher_spec.rb +8 -13
- data/tutorials/heat_watcher/test_observations/heat_watcher_spec/HeatWatcher_should_properly_report_critical_errors.json +7 -49
- data/tutorials/heat_watcher/test_observations/heat_watcher_spec/HeatWatcher_should_properly_report_warnings_and_switch_back_to_OK_status.json +9 -63
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c65fb7c7aa4cf11b582ae1620a91b522d88aaee3
|
4
|
+
data.tar.gz: fc50ff99e60d3c387394c886d48196722f056c6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfaa6acd7d9c78967231e194b652db630b271d07d6091a0bc7add63b8c598bf253ca97bd9451216d98203e5a896cd51a8789b999b1ceefd2a73928b7f03aba75
|
7
|
+
data.tar.gz: 72e3e53c5616f4948f8a0d0c2bbf0d758fa2df145a16161bf905421143bc49bebd1946c9596fbc4daf14a52b82f20d8edf3416e1018b6320263ea764ed1e6efe
|
data/bin/bond_reconcile.py
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
from __future__ import print_function
|
4
4
|
|
5
5
|
import os
|
6
|
-
import
|
7
|
-
import
|
6
|
+
import difflib
|
7
|
+
import string
|
8
|
+
import random
|
8
9
|
import sys
|
10
|
+
from bond_dialog import OptionDialog
|
9
11
|
|
10
12
|
try:
|
11
13
|
# Import bond safely
|
@@ -24,6 +26,8 @@ class ReconcileTool:
|
|
24
26
|
Base class for reconcile tools
|
25
27
|
"""
|
26
28
|
|
29
|
+
TMP_FILE_BASE_NAME = '/tmp/bond_tmp_'
|
30
|
+
|
27
31
|
@staticmethod
|
28
32
|
def select(reconcile_tool=None):
|
29
33
|
|
@@ -36,12 +40,15 @@ class ReconcileTool:
|
|
36
40
|
if reconcile_tool == 'console':
|
37
41
|
return ReconcileToolConsole()
|
38
42
|
|
43
|
+
if reconcile_tool == 'dialog':
|
44
|
+
return ReconcileToolDialog()
|
45
|
+
|
39
46
|
if reconcile_tool == 'kdiff3':
|
40
47
|
return ReconcileToolKdiff3()
|
41
48
|
|
42
49
|
if reconcile_tool is None:
|
43
50
|
# Look at the environment variable BOND_RECONCILE
|
44
|
-
reconcile_tool = os.environ.get('BOND_RECONCILE', '
|
51
|
+
reconcile_tool = os.environ.get('BOND_RECONCILE', 'console')
|
45
52
|
if reconcile_tool is not None:
|
46
53
|
return ReconcileTool.select(reconcile_tool)
|
47
54
|
|
@@ -59,16 +66,69 @@ class ReconcileTool:
|
|
59
66
|
"""
|
60
67
|
return os.system(cmd)
|
61
68
|
|
69
|
+
@staticmethod
|
70
|
+
def _get_user_input_console(prompt, options, single_char_options):
|
71
|
+
"""
|
72
|
+
Get input from the user using a console.
|
73
|
+
:param prompt: The main prompt string to display to the user.
|
74
|
+
:param options: A tuple of options to present to the user for them to select; must have
|
75
|
+
at least one. The last option is the default.
|
76
|
+
:param single_char_options: A tuple of the single-character versions of each of the
|
77
|
+
options supplied; this tuple must be the same length as ``options``,
|
78
|
+
and each single-character version must appear within the option itself.
|
79
|
+
:return: The user response, which is guaranteed to be one of the supplied ``options`` (if the user
|
80
|
+
input an invalid response the default option is used).
|
81
|
+
"""
|
82
|
+
opts_with_single_char = \
|
83
|
+
[string.replace(opt, char, '[' + char + ']', 1) for opt, char in zip(options, single_char_options)]
|
84
|
+
# Default option, highlight it in bold
|
85
|
+
opts_with_single_char[-1] = '\033[1m' + opts_with_single_char[-1] + '\033[0m'
|
86
|
+
response = raw_input(prompt + ' (' + ' | '.join(opts_with_single_char) + '): ')
|
87
|
+
if len(response) == 0: # No input; return the default
|
88
|
+
return options[-1]
|
89
|
+
elif len(response) == 1: # Single-character; find matching option
|
90
|
+
return next((opt for opt, char in zip(options, single_char_options) if char == response), options[-1])
|
91
|
+
else:
|
92
|
+
# Make sure response is a valid option; if not return the default
|
93
|
+
return next((opt for opt in options if opt == response), options[-1])
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def _get_user_input_dialog(prompt, options):
|
97
|
+
"""
|
98
|
+
Get input from the user using a dialog box.
|
99
|
+
:param prompt: The main prompt string to display to the user.
|
100
|
+
:param options: A tuple of options to present to the user for them to select; must have
|
101
|
+
at least one. The last option is the default.
|
102
|
+
:return: The user response, which is guaranteed to be one of the supplied ``options``
|
103
|
+
(if the user input an invalid response the default option is used).
|
104
|
+
"""
|
105
|
+
return OptionDialog.create_dialog_get_value(prompt, options)
|
106
|
+
|
62
107
|
@staticmethod
|
63
108
|
@spy_point(enabled_for_groups='bond_self_test',
|
64
109
|
require_agent_result=True,
|
110
|
+
excluded_keys=('extra_dialog_prompt','single_char_options'),
|
65
111
|
spy_result=True)
|
66
|
-
def
|
112
|
+
def _get_user_input(prompt, options, single_char_options, extra_dialog_prompt=''):
|
67
113
|
"""
|
68
|
-
|
114
|
+
Acquire input from the user. Defaults to using the console to ask for input. If
|
115
|
+
a console is not available, falls back to using a popup dialog window.
|
116
|
+
:param prompt: The main prompt string to display to the user.
|
117
|
+
:param options: A tuple of options to present to the user for them to select; must have
|
118
|
+
at least one. The last option is the default.
|
119
|
+
:param single_char_options: A tuple of the single-character versions of each of the
|
120
|
+
options supplied; this tuple must be the same length as ``options``,
|
121
|
+
and each single-character version must appear within the option itself.
|
122
|
+
:param extra_dialog_prompt: An extra message to display before the prompt only if the fallback
|
123
|
+
dialog box is used.
|
124
|
+
:return: The user response, which is guaranteed to be one of the supplied ``options`` (if the user
|
125
|
+
input an invalid response the default option is used).
|
69
126
|
"""
|
70
|
-
|
71
|
-
|
127
|
+
if sys.stdin.isatty(): # We use the console to retrieve input
|
128
|
+
return ReconcileTool._get_user_input_console(prompt, options, single_char_options)
|
129
|
+
else: # We use a dialog box to retrieve input
|
130
|
+
print('System console not found; using a dialog box to retrieve input instead.')
|
131
|
+
return ReconcileTool._get_user_input_dialog(extra_dialog_prompt + '\n' + prompt, options)
|
72
132
|
|
73
133
|
@staticmethod
|
74
134
|
@spy_point(enabled_for_groups='bond_self_test')
|
@@ -81,23 +141,26 @@ class ReconcileTool:
|
|
81
141
|
print(what)
|
82
142
|
|
83
143
|
@staticmethod
|
84
|
-
|
144
|
+
@spy_point(enabled_for_groups='bond_self_test', mock_only=True)
|
145
|
+
def _random_string():
|
85
146
|
"""
|
86
|
-
|
87
|
-
Return True if there are no diffs
|
147
|
+
Generate a short random string
|
88
148
|
"""
|
89
|
-
|
90
|
-
return (0 == ReconcileTool._invoke_command('diff -u -b "{0}" "{1}" >"{2}"'.format(reference_file,
|
91
|
-
current_file,
|
92
|
-
diff_file)))
|
149
|
+
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))
|
93
150
|
|
94
151
|
@staticmethod
|
95
|
-
def
|
96
|
-
flavor):
|
152
|
+
def _tmp_file_name(flavor):
|
97
153
|
"""
|
98
|
-
The name of the
|
154
|
+
The name of the temporary file to use with a flavor-specific
|
155
|
+
(e.g. diff, curr, ref) ending.
|
99
156
|
"""
|
100
|
-
|
157
|
+
# We include a short random string to avoid potential collisions
|
158
|
+
return ReconcileTool.TMP_FILE_BASE_NAME + ReconcileTool._random_string() + "." + flavor
|
159
|
+
|
160
|
+
@staticmethod
|
161
|
+
@spy_point(enabled_for_groups='bond_self_test')
|
162
|
+
def _compute_diff(reference_lines, current_lines):
|
163
|
+
return list(difflib.unified_diff(reference_lines, current_lines, 'reference', 'current'))
|
101
164
|
|
102
165
|
def __init__(self):
|
103
166
|
pass
|
@@ -105,66 +168,60 @@ class ReconcileTool:
|
|
105
168
|
def reconcile(self,
|
106
169
|
test_name,
|
107
170
|
reference_file,
|
108
|
-
|
171
|
+
current_lines,
|
109
172
|
no_save=None):
|
110
173
|
"""
|
111
174
|
Reconcile the differences
|
112
175
|
@param test_name: the name of the test (for messages)
|
113
176
|
@param reference_file: the name of the reference observation file
|
114
|
-
@param
|
177
|
+
@param current_lines: a list of the lines which make up the current set of observations
|
115
178
|
:param no_save: if present, then disallows saving a new reference file.
|
116
179
|
This parameter should be a string explaining why saving is disallowed.
|
117
180
|
"""
|
118
181
|
|
119
|
-
if
|
182
|
+
if os.path.isfile(reference_file):
|
183
|
+
with open(reference_file, 'r') as f:
|
184
|
+
reference_lines = f.readlines()
|
185
|
+
else:
|
120
186
|
# if we do not have the reference file, pretend we have an empty one
|
121
187
|
ReconcileTool._print('WARNING: No reference observation file found for {}: {}'.format(test_name, reference_file))
|
122
|
-
|
123
|
-
with open(reference_file, 'w') as f:
|
124
|
-
pass
|
125
|
-
# We continue
|
188
|
+
reference_lines = list()
|
126
189
|
|
127
190
|
# Compute a quick difference
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
else:
|
146
|
-
ReconcileTool._print('Saving updated reference observation file for {}'.format(test_name))
|
147
|
-
shutil.move(merged_file, reference_file)
|
148
|
-
return True
|
191
|
+
unified_diff = self._compute_diff(reference_lines, current_lines)
|
192
|
+
|
193
|
+
if len(unified_diff) == 0:
|
194
|
+
# There are no differences
|
195
|
+
return True
|
196
|
+
|
197
|
+
# There are differences
|
198
|
+
merged_lines = self.invoke_tool(test_name,
|
199
|
+
reference_lines,
|
200
|
+
current_lines,
|
201
|
+
unified_diff,
|
202
|
+
no_save=no_save)
|
203
|
+
if merged_lines is not None:
|
204
|
+
# Accepted differences
|
205
|
+
if no_save:
|
206
|
+
ReconcileTool._print("Not saving reference observation file for {}: {}".format(test_name,
|
207
|
+
no_save))
|
149
208
|
else:
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
209
|
+
ReconcileTool._print('Saving updated reference observation file for {}'.format(test_name))
|
210
|
+
if os.path.isfile(reference_file):
|
211
|
+
os.unlink(reference_file)
|
212
|
+
with open(reference_file, 'w') as f:
|
213
|
+
f.writelines(merged_lines)
|
214
|
+
return True
|
215
|
+
else:
|
216
|
+
return False
|
158
217
|
|
159
218
|
def show_diff(self,
|
160
219
|
test_name,
|
161
|
-
|
220
|
+
unified_diff):
|
162
221
|
"""
|
163
|
-
Show the diff, and return it
|
222
|
+
Show the lines of the diff, and return it as a string
|
164
223
|
"""
|
165
|
-
diffs = ''
|
166
|
-
with open(diff_file, 'r') as f:
|
167
|
-
diffs = f.read()
|
224
|
+
diffs = ''.join(unified_diff)
|
168
225
|
if diffs:
|
169
226
|
ReconcileTool._print('There were differences in observations for {}: '.format(test_name))
|
170
227
|
ReconcileTool._print(diffs)
|
@@ -175,14 +232,14 @@ class ReconcileTool:
|
|
175
232
|
return diffs
|
176
233
|
|
177
234
|
def invoke_tool(self, test_name,
|
178
|
-
|
179
|
-
|
180
|
-
|
235
|
+
reference_lines,
|
236
|
+
current_lines,
|
237
|
+
unified_diff,
|
181
238
|
no_save=None):
|
182
239
|
"""
|
183
240
|
Invoke the actual tool
|
184
241
|
@param
|
185
|
-
@return either False, for a failed merge, or
|
242
|
+
@return either False, for a failed merge, or a list of the new lines to use as the reference
|
186
243
|
"""
|
187
244
|
assert False, 'Must override'
|
188
245
|
|
@@ -194,12 +251,12 @@ class ReconcileToolAbort(ReconcileTool):
|
|
194
251
|
|
195
252
|
def invoke_tool(self,
|
196
253
|
test_name,
|
197
|
-
|
198
|
-
|
199
|
-
|
254
|
+
reference_lines,
|
255
|
+
current_lines,
|
256
|
+
unified_diff,
|
200
257
|
no_save=None):
|
201
258
|
if not no_save:
|
202
|
-
self.show_diff(test_name,
|
259
|
+
self.show_diff(test_name, unified_diff)
|
203
260
|
ReconcileTool._print('Aborting (reconcile=abort) due to differences for test {}'.format(test_name))
|
204
261
|
return None
|
205
262
|
|
@@ -211,60 +268,70 @@ class ReconcileToolAccept(ReconcileTool):
|
|
211
268
|
|
212
269
|
def invoke_tool(self,
|
213
270
|
test_name,
|
214
|
-
|
215
|
-
|
216
|
-
|
271
|
+
reference_lines,
|
272
|
+
current_lines,
|
273
|
+
unified_diff,
|
217
274
|
no_save=None):
|
218
|
-
|
275
|
+
self.show_diff(test_name, unified_diff)
|
219
276
|
if not no_save:
|
220
277
|
ReconcileTool._print('Accepting (reconcile=accept) differences for test {}'.format(test_name))
|
221
|
-
return
|
278
|
+
return current_lines
|
222
279
|
|
223
280
|
|
224
281
|
class ReconcileToolConsole(ReconcileTool):
|
225
282
|
"""
|
226
|
-
Uses diff and console prompts to accept the changes
|
283
|
+
Uses diff and console prompts to accept the changes. Falls back to a dialog window
|
284
|
+
if no console is available for input.
|
227
285
|
"""
|
228
286
|
|
229
287
|
def invoke_tool(self,
|
230
288
|
test_name,
|
231
|
-
|
232
|
-
|
233
|
-
|
289
|
+
reference_lines,
|
290
|
+
current_lines,
|
291
|
+
unified_diff,
|
234
292
|
no_save=None):
|
235
293
|
|
294
|
+
extra_msg = None
|
236
295
|
while True:
|
237
296
|
if no_save:
|
238
|
-
prompt = 'Observations are shown for {}. Saving them not allowed because test failed.'
|
239
|
-
|
240
|
-
|
297
|
+
prompt = 'Observations are shown for {}. Saving them not allowed because test failed. ' \
|
298
|
+
'Use the diff option to show the differences.'.format(test_name)
|
299
|
+
response = self._input(prompt, ('kdiff3', 'diff', 'errors', 'continue'),
|
300
|
+
('k', 'd', 'e', 'c'),
|
301
|
+
extra_msg if extra_msg else '\n'.join(current_lines))
|
241
302
|
else:
|
242
303
|
# Show the diff
|
243
|
-
self.show_diff(test_name,
|
244
|
-
prompt = 'Do you want to accept the changes ({})
|
245
|
-
|
246
|
-
response = ReconcileTool._read_console(prompt)
|
304
|
+
diff = self.show_diff(test_name, unified_diff)
|
305
|
+
prompt = 'Do you want to accept the changes ({})?'.format(test_name)
|
306
|
+
response = self._input(prompt, ('kdiff3', 'yes', 'no'), ('k', 'y', 'n'), diff)
|
247
307
|
|
248
|
-
if response == '
|
249
|
-
return ReconcileToolKdiff3().invoke_tool(test_name,
|
308
|
+
if response == 'kdiff3':
|
309
|
+
return ReconcileToolKdiff3().invoke_tool(test_name, reference_lines, current_lines, unified_diff,
|
250
310
|
no_save=no_save)
|
251
|
-
|
252
|
-
|
253
|
-
self.show_diff(test_name, diff_file)
|
311
|
+
elif response == 'diff':
|
312
|
+
extra_msg = self.show_diff(test_name, unified_diff)
|
254
313
|
continue
|
255
|
-
|
256
|
-
|
257
|
-
ReconcileTool._print(
|
314
|
+
elif response == 'errors':
|
315
|
+
extra_msg = "Test {} had errors:\n{}".format(test_name, no_save)
|
316
|
+
ReconcileTool._print('\033[91m' + extra_msg + '\033[0m')
|
258
317
|
continue
|
259
|
-
|
260
|
-
if response == 'y' and not no_save:
|
318
|
+
elif response == 'yes' and not no_save:
|
261
319
|
ReconcileTool._print('Accepting differences for test {}'.format(test_name))
|
262
|
-
return
|
263
|
-
|
264
|
-
if not no_save:
|
320
|
+
return current_lines
|
321
|
+
elif not no_save:
|
265
322
|
ReconcileTool._print('Rejecting differences for test {}'.format(test_name))
|
266
323
|
return None
|
267
324
|
|
325
|
+
def _input(self, prompt, options, single_char_options, extra_dialog_prompt=None):
|
326
|
+
return ReconcileTool._get_user_input(prompt, options, single_char_options, extra_dialog_prompt)
|
327
|
+
|
328
|
+
|
329
|
+
class ReconcileToolDialog(ReconcileToolConsole):
|
330
|
+
"""
|
331
|
+
Merge with a dialog window.
|
332
|
+
"""
|
333
|
+
def _input(self, prompt, options, single_char_options, extra_dialog_prompt=None):
|
334
|
+
return ReconcileTool._get_user_input_dialog(extra_dialog_prompt + '\n' + prompt, options)
|
268
335
|
|
269
336
|
|
270
337
|
class ReconcileToolKdiff3(ReconcileTool):
|
@@ -274,57 +341,77 @@ class ReconcileToolKdiff3(ReconcileTool):
|
|
274
341
|
|
275
342
|
def invoke_tool(self,
|
276
343
|
test_name,
|
277
|
-
|
278
|
-
|
279
|
-
|
344
|
+
reference_lines,
|
345
|
+
current_lines,
|
346
|
+
unified_diff,
|
280
347
|
no_save=None):
|
281
348
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
349
|
+
# Save the current lines out to a temporary file to use with kdiff3
|
350
|
+
current_file = self._tmp_file_name('curr')
|
351
|
+
reference_file = self._tmp_file_name('ref')
|
352
|
+
try:
|
353
|
+
with open(current_file, 'w') as f:
|
354
|
+
f.writelines(current_lines)
|
355
|
+
with open(reference_file, 'w') as f:
|
356
|
+
f.writelines(reference_lines)
|
357
|
+
|
358
|
+
merged_file = None
|
359
|
+
if no_save:
|
360
|
+
response = ReconcileTool._get_user_input(
|
361
|
+
"\n!!! MERGING NOT ALLOWED for {}: {}. Want to start kdiff3?".format(test_name, no_save),
|
362
|
+
('yes', 'no'), ('y', 'n'))
|
363
|
+
if response != 'yes':
|
364
|
+
return None
|
365
|
+
|
366
|
+
cmd = ('kdiff3 "{reference_file}" --L1 "{test_name}_REFERENCE" '
|
367
|
+
'"{current_file}" --L2 "{test_name}_CURRENT" ').format(
|
368
|
+
reference_file=reference_file,
|
369
|
+
current_file=current_file,
|
370
|
+
test_name=test_name)
|
371
|
+
ReconcileTool._invoke_command(cmd)
|
287
372
|
return None
|
288
373
|
|
289
|
-
cmd = ('kdiff3 "{reference_file}" --L1 "{test_name}_REFERENCE" '
|
290
|
-
'"{current_file}" --L2 "{test_name}_CURRENT" ').format(
|
291
|
-
reference_file=reference_file,
|
292
|
-
current_file=current_file,
|
293
|
-
test_name=test_name)
|
294
|
-
else:
|
295
|
-
merged_file = self._aux_file_name(current_file, 'merged')
|
296
|
-
|
297
|
-
cmd = ('kdiff3 -m "{reference_file}" --L1 "{test_name}_REFERENCE" '
|
298
|
-
'"{current_file}" --L2 "{test_name}_CURRENT" '
|
299
|
-
' -o "{merged_file}"').format(reference_file=reference_file,
|
300
|
-
current_file=current_file,
|
301
|
-
merged_file=merged_file,
|
302
|
-
test_name=test_name)
|
303
|
-
|
304
|
-
print(cmd)
|
305
|
-
code = ReconcileTool._invoke_command(cmd)
|
306
|
-
if no_save:
|
307
|
-
return None
|
308
|
-
else:
|
309
|
-
if code == 0 :
|
310
|
-
# Merged ok
|
311
|
-
return merged_file
|
312
374
|
else:
|
313
|
-
|
314
|
-
|
315
|
-
|
375
|
+
merged_file = self._tmp_file_name('merged')
|
376
|
+
cmd = ('kdiff3 -m "{reference_file}" --L1 "{test_name}_REFERENCE" '
|
377
|
+
'"{current_file}" --L2 "{test_name}_CURRENT" '
|
378
|
+
' -o "{merged_file}"').format(reference_file=reference_file,
|
379
|
+
current_file=current_file,
|
380
|
+
merged_file=merged_file,
|
381
|
+
test_name=test_name)
|
382
|
+
|
383
|
+
code = ReconcileTool._invoke_command(cmd)
|
384
|
+
if code == 0:
|
385
|
+
# Merged ok
|
386
|
+
with open(merged_file, 'r') as f:
|
387
|
+
merged_lines = f.readlines()
|
388
|
+
message = 'Merge successful; saving a new reference file. '
|
389
|
+
ret = merged_lines
|
390
|
+
else:
|
391
|
+
message = 'Merge unsuccessful; not saving a new reference file. '
|
392
|
+
ret = None
|
393
|
+
|
394
|
+
ReconcileTool._get_user_input(message, ('continue',), ('c',))
|
395
|
+
return ret
|
396
|
+
finally:
|
397
|
+
if os.path.isfile(current_file):
|
398
|
+
os.unlink(current_file)
|
399
|
+
if os.path.isfile(reference_file):
|
400
|
+
os.unlink(reference_file)
|
401
|
+
if merged_file is not None and os.path.isfile(merged_file):
|
402
|
+
os.unlink(merged_file)
|
316
403
|
|
317
404
|
|
318
405
|
def reconcile_observations(settings,
|
319
406
|
test_name,
|
320
407
|
reference_file,
|
321
|
-
|
408
|
+
current_lines,
|
322
409
|
no_save=None):
|
323
410
|
"""
|
324
411
|
Reconcile the observations
|
325
412
|
:param settings: a settings object
|
326
413
|
:param reference_file: the reference file
|
327
|
-
:param
|
414
|
+
:param current_lines: a list of all of the lines in the current set of observations
|
328
415
|
:param no_save: If present, then saving of new references is not allowed. This parameter
|
329
416
|
should be a short string explaining why saving is not allowed.
|
330
417
|
:return:
|
@@ -333,7 +420,7 @@ def reconcile_observations(settings,
|
|
333
420
|
reconcile_tool = ReconcileTool.select(settings.get('reconcile'))
|
334
421
|
return reconcile_tool.reconcile(test_name,
|
335
422
|
reference_file,
|
336
|
-
|
423
|
+
current_lines,
|
337
424
|
no_save=no_save)
|
338
425
|
|
339
426
|
|
@@ -361,6 +448,9 @@ if __name__ == '__main__':
|
|
361
448
|
print('The current file does not exist: {}'.format(opts.current), file=sys.stderr)
|
362
449
|
sys.exit(1)
|
363
450
|
|
451
|
+
with open(opts.current, 'r') as f:
|
452
|
+
current_lines = f.readlines()
|
453
|
+
os.unlink(opts.current)
|
364
454
|
|
365
455
|
# Guess the test name from the current file
|
366
456
|
if opts.test:
|
@@ -378,7 +468,7 @@ if __name__ == '__main__':
|
|
378
468
|
if reconcile_observations(main_settings,
|
379
469
|
main_test_name,
|
380
470
|
opts.reference,
|
381
|
-
|
471
|
+
current_lines,
|
382
472
|
no_save=opts.no_save):
|
383
473
|
sys.exit(0)
|
384
474
|
else:
|