bond-spy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +27 -0
  7. data/README.rst +65 -0
  8. data/Rakefile +6 -0
  9. data/bin/bond_reconcile.py +385 -0
  10. data/bin/setup +5 -0
  11. data/bond.gemspec +36 -0
  12. data/lib/bond.rb +469 -0
  13. data/lib/bond/spec_helper.rb +32 -0
  14. data/lib/bond/targetable.rb +210 -0
  15. data/lib/bond/version.rb +3 -0
  16. data/spec/bond_spec.rb +158 -0
  17. data/spec/bond_targetable_spec.rb +202 -0
  18. data/spec/spec_helper.rb +2 -0
  19. data/spec/test_observations/.gitignore +2 -0
  20. data/spec/test_observations/bond_spec/Bond_with_agents_should_call_doers_before_returning_result.json +14 -0
  21. data/spec/test_observations/bond_spec/Bond_with_agents_should_call_the_function_passed_as_result_if_it_is_callable.json +23 -0
  22. data/spec/test_observations/bond_spec/Bond_with_agents_should_correctly_call_a_single_doer_if_filter_criteria_are_met.json +10 -0
  23. data/spec/test_observations/bond_spec/Bond_with_agents_should_correctly_call_multiple_doers.json +13 -0
  24. data/spec/test_observations/bond_spec/Bond_with_agents_should_not_call_doers_of_overriden_agents.json +8 -0
  25. data/spec/test_observations/bond_spec/Bond_with_agents_should_override_old_agents_with_newer_agents.json +0 -0
  26. data/spec/test_observations/bond_spec/Bond_with_agents_should_throw_an_exception_if_specified_by_agent.json +9 -0
  27. data/spec/test_observations/bond_spec/Bond_with_agents_should_throw_the_result_of_the_value_passed_to_exception_if_callable.json +10 -0
  28. data/spec/test_observations/bond_spec/Bond_with_agents_should_work_with_multiple_agents_for_different_spy_points.json +23 -0
  29. data/spec/test_observations/bond_spec/Bond_with_agents_with_filters_should_override_old_agents_with_newer_agents_unless_theott8glo1xn.json +22 -0
  30. data/spec/test_observations/bond_spec/Bond_with_agents_with_filters_should_respect_combinations_of_filters.json +37 -0
  31. data/spec/test_observations/bond_spec/Bond_with_agents_with_filters_should_respect_function_filters.json +16 -0
  32. data/spec/test_observations/bond_spec/Bond_with_agents_with_filters_should_respect_single_key_value_filters_of_all_types.json +121 -0
  33. data/spec/test_observations/bond_spec/Bond_without_any_agents_should_correctly_log_nested_hashes_and_arrays_with_hash_sorting.json +31 -0
  34. data/spec/test_observations/bond_spec/Bond_without_any_agents_should_correctly_log_some_normal_arguments_with_a_spy_point_name.json +12 -0
  35. data/spec/test_observations/bond_spec/Bond_without_any_agents_should_correctly_log_some_normal_arguments_without_a_spy_point_name.json +10 -0
  36. data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_continues_to_the_method_when_agent_result_continue_is_returned.json +10 -0
  37. data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_continues_to_the_method_when_agent_result_none_is_returned.json +14 -0
  38. data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_passes_through_blocks.json +10 -0
  39. data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_returns_nil__and_mocks__when_an_agent_returns_nil.json +11 -0
  40. data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_spies_private_methods.json +6 -0
  41. data/spec/test_observations/bond_targetable_spec/BondTargetable_correctly_spies_protected_methods.json +6 -0
  42. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_class_method.json +7 -0
  43. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_method_with_variabl19nhijeqoo.json +13 -0
  44. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_mix_of_positional_a6qc3d4el92.json +33 -0
  45. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_mix_of_positional_aott8glo1xn.json +20 -0
  46. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_mix_of_required_andbcgjq06had.json +17 -0
  47. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_a_normal_method.json +7 -0
  48. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_all_optional_keyword_arguments.json +10 -0
  49. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_argument_types_correctly_spies_on_variable_keyword_arguments.json +22 -0
  50. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_changes_the_spy_point_naj4gnwvcu8n.json +5 -0
  51. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_errors_when_mocking_is_r9j7wklng0z.json +6 -0
  52. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_ignores_excluded_keys.json +10 -0
  53. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_mocks_when_one_is_specified.json +10 -0
  54. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_spies_the_return_value_w19nhijeqoo.json +10 -0
  55. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_different_spy_point_parameters_correctly_spies_the_return_value_ww8esw1qdxc.json +10 -0
  56. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_modules_correctly_spies_on_included_module_methods.json +6 -0
  57. data/spec/test_observations/bond_targetable_spec/BondTargetable_with_modules_correctly_spies_on_module_methods.json +7 -0
  58. data/tutorials/binary_search_tree/bst.rb +120 -0
  59. data/tutorials/binary_search_tree/bst_spec.rb +82 -0
  60. data/tutorials/binary_search_tree/run_tests.sh +4 -0
  61. data/tutorials/binary_search_tree/test_observations/.gitignore +2 -0
  62. data/tutorials/binary_search_tree/test_observations/bst_spec/Node_should_add_nodes_to_the_BST_correctly__testing_with_Bond.json +20 -0
  63. data/tutorials/binary_search_tree/test_observations/bst_spec/Node_should_add_nodes_to_the_BST_correctly__testing_without_Bond.json +3 -0
  64. data/tutorials/binary_search_tree/test_observations/bst_spec/Node_should_correctly_delete_nodes_from_the_BST.json +29 -0
  65. data/tutorials/heat_watcher/heat_watcher.rb +107 -0
  66. data/tutorials/heat_watcher/heat_watcher_spec.rb +116 -0
  67. data/tutorials/heat_watcher/run_tests.sh +4 -0
  68. data/tutorials/heat_watcher/test_observations/.gitignore +2 -0
  69. data/tutorials/heat_watcher/test_observations/heat_watcher_spec/HeatWatcher_should_properly_report_critical_errors.json +142 -0
  70. data/tutorials/heat_watcher/test_observations/heat_watcher_spec/HeatWatcher_should_properly_report_warnings_and_switch_back_to_OK_status.json +132 -0
  71. metadata +211 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff046742322ed31d8ac6ce9b5145d67072af7593
4
+ data.tar.gz: 95c911f15617180b17afc73b0812c56225cb8112
5
+ SHA512:
6
+ metadata.gz: e168396ccc1ab5a002be950f5680b26b31a26186ef0e6003b3bd54bc511591973367128683d2da958627ce3ba65e2dfbb1db71c68f66851d19ff174c9b4f5b56
7
+ data.tar.gz: f6511ca661d7395403668d315dfbc96f47b9578d1757528a8c25b47e1e2dfe56b963590fa237643e870788cc5bb8ed1095bfb8eec8972adec535c42b4acf398b
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ # Ruby specific
2
+ *.gem
3
+ /.bundle/
4
+ /.yardoc
5
+ /Gemfile.lock
6
+ /_yardoc/
7
+ /coverage/
8
+ /doc/
9
+ /pkg/
10
+ /spec/reports/
11
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup=markdown --no-private --protected
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bond.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2015, Conviva Inc.
2
+ Copyright (c) 2015, George Necula, Erik Krogen
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
+
25
+ The views and conclusions contained in the software and documentation are those
26
+ of the authors and should not be interpreted as representing official policies,
27
+ either expressed or implied, of the FreeBSD Project.
data/README.rst ADDED
@@ -0,0 +1,65 @@
1
+ Bond
2
+ .......................
3
+
4
+ For more about Bond and usage instructions, see the main `documentation page <http://necula01.github.io/bond/>`_.
5
+
6
+ Installation
7
+ -----------------------
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ .. code::
12
+
13
+ gem 'bond-spy'
14
+
15
+ And then execute:
16
+
17
+ .. code:: bash
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ .. code:: bash
24
+
25
+ $ gem install bond-spy
26
+
27
+ Please note that the minimum version of Ruby that Bond supports is 2.1.
28
+
29
+ Development
30
+ -----------------------
31
+
32
+ After checking out the repo, run ``bin/setup`` to install dependencies. Then, run ``rake spec``
33
+ to run the tests.
34
+
35
+ API documentation is built using YARD; simply running ``yard`` will generate the documentation.
36
+ However, for the full suite of documentation including examples, you should build from the
37
+ project main directory (one level up), which will generate and integrate YARD documentation.
38
+ To do this, simply run ``make docs`` from the main directory.
39
+
40
+ .. rst_newVersionInstructionsStart
41
+
42
+ To install this gem onto your local machine, run ``bundle exec rake install``. To release a
43
+ new version, update the version number in ``bond/version.rb`` and the date in ``bond.gemspec``,
44
+ commit your changes, and tag the commit with ``rbond-vX.X.X``. Please include '[rbond]' as the
45
+ first part of any commit message related to the Ruby version of Bond to differentiate it from
46
+ commits related to other parts of Bond. Push your commit up (including tags), build the gem
47
+ using ``gem build bond.gemspec``, and run ``gem push bond-spy-X.X.X.gem`` to release the .gem
48
+ file to `rubygems.org <https://rubygems.org>`_. For example:
49
+
50
+ .. code:: bash
51
+
52
+ $ git commit -am "[rbond] Release version 1.0.2"
53
+ $ git tag rbond-v1.0.2
54
+ $ git push origin --tags
55
+ $ git push origin
56
+ $ gem build bond.gemspec
57
+ $ gem push bond-spy-1.0.2.gem
58
+
59
+ .. rst_newVersionInstructionsEnd
60
+
61
+ Contributing
62
+ -----------------------
63
+
64
+ Bug reports and pull requests are welcome on `GitHub <https://github.com/necula01/bond>`_.
65
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env python
2
+
3
+ from __future__ import print_function
4
+
5
+ import os
6
+ import re
7
+ import shutil
8
+ import sys
9
+
10
+ try:
11
+ # Import bond safely
12
+ from bond import spy_point
13
+ except ImportError:
14
+ spy_point = lambda **kw: lambda f: f
15
+
16
+
17
+ """
18
+ Reconcile the reference observations with the current ones
19
+ """
20
+
21
+
22
+ class ReconcileTool:
23
+ """
24
+ Base class for reconcile tools
25
+ """
26
+
27
+ @staticmethod
28
+ def select(reconcile_tool=None):
29
+
30
+ if reconcile_tool == 'accept':
31
+ return ReconcileToolAccept()
32
+
33
+ if reconcile_tool == 'abort':
34
+ return ReconcileToolAbort()
35
+
36
+ if reconcile_tool == 'console':
37
+ return ReconcileToolConsole()
38
+
39
+ if reconcile_tool == 'kdiff3':
40
+ return ReconcileToolKdiff3()
41
+
42
+ if reconcile_tool is None:
43
+ # Look at the environment variable BOND_RECONCILE
44
+ reconcile_tool = os.environ.get('BOND_RECONCILE', 'abort')
45
+ if reconcile_tool is not None:
46
+ return ReconcileTool.select(reconcile_tool)
47
+
48
+ assert False, 'Unrecognized bond_reconcile tool name: {}'.format(reconcile_tool)
49
+
50
+
51
+ @staticmethod
52
+ @spy_point(enabled_for_groups='bond_self_test',
53
+ require_agent_result=True)
54
+ def _invoke_command(cmd):
55
+ """
56
+ Invoke a shell command. Return the exit code.
57
+ :param cmd:
58
+ :return:
59
+ """
60
+ return os.system(cmd)
61
+
62
+ @staticmethod
63
+ @spy_point(enabled_for_groups='bond_self_test',
64
+ require_agent_result=True,
65
+ spy_result=True)
66
+ def _read_console(prompt):
67
+ """
68
+ A function to read from the console
69
+ """
70
+ return raw_input(prompt)
71
+
72
+
73
+ @staticmethod
74
+ @spy_point(enabled_for_groups='bond_self_test')
75
+ def _print(what):
76
+ """
77
+ A function to do the printing, so we can spy on it
78
+ :param what:
79
+ :return:
80
+ """
81
+ print(what)
82
+
83
+ @staticmethod
84
+ def _quick_diff(reference_file, current_file, diff_file):
85
+ """
86
+ Compute a diff between files and save it into a diff_file.
87
+ Return True if there are no diffs
88
+ """
89
+ # TODO: implicit dependency on a 'diff' command line tool with the same usage syntax that you're expecting
90
+ return (0 == ReconcileTool._invoke_command('diff -u -b "{0}" "{1}" >"{2}"'.format(reference_file,
91
+ current_file,
92
+ diff_file)))
93
+
94
+ @staticmethod
95
+ def _aux_file_name(current_file,
96
+ flavor):
97
+ """
98
+ The name of the auxiliary (e.g., diff, merged) file to use
99
+ """
100
+ return current_file + "." + flavor
101
+
102
+ def __init__(self):
103
+ pass
104
+
105
+ def reconcile(self,
106
+ test_name,
107
+ reference_file,
108
+ current_file,
109
+ no_save=None):
110
+ """
111
+ Reconcile the differences
112
+ @param test_name: the name of the test (for messages)
113
+ @param reference_file: the name of the reference observation file
114
+ @param current_file: the name of the current observation file
115
+ :param no_save: if present, then disallows saving a new reference file.
116
+ This parameter should be a string explaining why saving is disallowed.
117
+ """
118
+
119
+ if not os.path.isfile(reference_file):
120
+ # if we do not have the reference file, pretend we have an empty one
121
+ 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
126
+
127
+ # Compute a quick difference
128
+ diff_file = self._aux_file_name(current_file, "diff")
129
+ try:
130
+ if self._quick_diff(reference_file, current_file, diff_file):
131
+ # There are no differences
132
+ return True
133
+
134
+ # There are differences
135
+ merged_file = self.invoke_tool(test_name,
136
+ reference_file,
137
+ current_file,
138
+ diff_file,
139
+ no_save=no_save)
140
+ if merged_file is not None:
141
+ # Accepted differences
142
+ if no_save:
143
+ ReconcileTool._print("Not saving reference observation file for {}: {}".format(test_name,
144
+ no_save))
145
+ else:
146
+ ReconcileTool._print('Saving updated reference observation file for {}'.format(test_name))
147
+ shutil.move(merged_file, reference_file)
148
+ return True
149
+ else:
150
+ return False
151
+
152
+ finally:
153
+ # Delete the files
154
+ if os.path.isfile(diff_file):
155
+ os.unlink(diff_file)
156
+ if os.path.isfile(current_file):
157
+ os.unlink(current_file)
158
+
159
+ def show_diff(self,
160
+ test_name,
161
+ diff_file):
162
+ """
163
+ Show the diff, and return it
164
+ """
165
+ diffs = ''
166
+ with open(diff_file, 'r') as f:
167
+ diffs = f.read()
168
+ if diffs:
169
+ ReconcileTool._print('There were differences in observations for {}: '.format(test_name))
170
+ ReconcileTool._print(diffs)
171
+ # Print it again at the end; makes it easy to see in the console what test just failed
172
+ ReconcileTool._print('There were differences in observations for {}: '.format(test_name))
173
+ else:
174
+ ReconcileTool._print('No differences in observations for {}: '.format(test_name))
175
+ return diffs
176
+
177
+ def invoke_tool(self, test_name,
178
+ reference_file,
179
+ current_file,
180
+ diff_file,
181
+ no_save=None):
182
+ """
183
+ Invoke the actual tool
184
+ @param
185
+ @return either False, for a failed merge, or the name of the file to use as the new reference
186
+ """
187
+ assert False, 'Must override'
188
+
189
+
190
+ class ReconcileToolAbort(ReconcileTool):
191
+ """
192
+ Abort all merges
193
+ """
194
+
195
+ def invoke_tool(self,
196
+ test_name,
197
+ reference_file,
198
+ current_file,
199
+ diff_file,
200
+ no_save=None):
201
+ if not no_save:
202
+ self.show_diff(test_name, diff_file)
203
+ ReconcileTool._print('Aborting (reconcile=abort) due to differences for test {}'.format(test_name))
204
+ return None
205
+
206
+
207
+ class ReconcileToolAccept(ReconcileTool):
208
+ """
209
+ Accept all changes
210
+ """
211
+
212
+ def invoke_tool(self,
213
+ test_name,
214
+ reference_file,
215
+ current_file,
216
+ diff_file,
217
+ no_save=None):
218
+ diffs = self.show_diff(test_name, diff_file)
219
+ if not no_save:
220
+ ReconcileTool._print('Accepting (reconcile=accept) differences for test {}'.format(test_name))
221
+ return current_file
222
+
223
+
224
+ class ReconcileToolConsole(ReconcileTool):
225
+ """
226
+ Uses diff and console prompts to accept the changes
227
+ """
228
+
229
+ def invoke_tool(self,
230
+ test_name,
231
+ reference_file,
232
+ current_file,
233
+ diff_file,
234
+ no_save=None):
235
+
236
+ while True:
237
+ if no_save:
238
+ prompt = 'Observations are shown for {}. Saving them not allowed because test failed.'.format(
239
+ test_name,
240
+ ) + ' Use the diff option to show the differences. ([k]diff3 | [d]iff | [e] errors | *): '
241
+ else:
242
+ # Show the diff
243
+ self.show_diff(test_name, diff_file)
244
+ prompt = 'Do you want to accept the changes ({}) ? ( [y]es | [k]diff3 | *): '.format(test_name)
245
+
246
+ response = ReconcileTool._read_console(prompt)
247
+
248
+ if response == 'k':
249
+ return ReconcileToolKdiff3().invoke_tool(test_name, reference_file, current_file, diff_file,
250
+ no_save=no_save)
251
+
252
+ if response == 'd' and no_save:
253
+ self.show_diff(test_name, diff_file)
254
+ continue
255
+
256
+ if response == 'e' and no_save:
257
+ ReconcileTool._print("Test {} had errors:\n{}".format(test_name, no_save))
258
+ continue
259
+
260
+ if response == 'y' and not no_save:
261
+ ReconcileTool._print('Accepting differences for test {}'.format(test_name))
262
+ return current_file
263
+
264
+ if not no_save:
265
+ ReconcileTool._print('Rejecting differences for test {}'.format(test_name))
266
+ return None
267
+
268
+
269
+
270
+ class ReconcileToolKdiff3(ReconcileTool):
271
+ """
272
+ Merge with kdiff3
273
+ """
274
+
275
+ def invoke_tool(self,
276
+ test_name,
277
+ reference_file,
278
+ current_file,
279
+ diff_file,
280
+ no_save=None):
281
+
282
+ if no_save:
283
+ response = ReconcileTool._read_console(
284
+ "\n!!! MERGING NOT ALLOWED for {}: {}. Want to start kdiff3? ([y] | *): ".format(test_name,
285
+ no_save))
286
+ if response != "y":
287
+ return None
288
+
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
+ else:
313
+ if os.path.isfile(merged_file):
314
+ os.unlink(merged_file)
315
+ return None
316
+
317
+
318
+ def reconcile_observations(settings,
319
+ test_name,
320
+ reference_file,
321
+ current_file,
322
+ no_save=None):
323
+ """
324
+ Reconcile the observations
325
+ :param settings: a settings object
326
+ :param reference_file: the reference file
327
+ :param current_file: the current file with observations
328
+ :param no_save: If present, then saving of new references is not allowed. This parameter
329
+ should be a short string explaining why saving is not allowed.
330
+ :return:
331
+ """
332
+
333
+ reconcile_tool = ReconcileTool.select(settings.get('reconcile'))
334
+ return reconcile_tool.reconcile(test_name,
335
+ reference_file,
336
+ current_file,
337
+ no_save=no_save)
338
+
339
+
340
+ if __name__ == '__main__':
341
+ import optparse
342
+
343
+ optParser = optparse.OptionParser(usage=os.path.basename(__file__),
344
+ description='Compare and reconciles differences in Bond observation files')
345
+
346
+ optParser.add_option('--reconcile', dest='reconcile', action='store', default=None,
347
+ help='The reconcile tool to use. Available: accept, abort, console, kdiff3.')
348
+ optParser.add_option('--reference', dest='reference', action='store', default=None,
349
+ help='The reference observation file')
350
+ optParser.add_option('--current', dest='current', action='store', default=None,
351
+ help='The current observation file')
352
+ optParser.add_option('--test', dest='test', action='store', default=None,
353
+ help='The name of the test (for UI). Default is to extract from --current')
354
+ optParser.add_option('--no-save', dest='no_save', action='store', default=None,
355
+ help='If given, the reason why saving of new references is not allowed')
356
+ (opts, args) = optParser.parse_args()
357
+ if opts.reference is None:
358
+ sys.exit(1)
359
+
360
+ if opts.current is None or not os.path.isfile(opts.current):
361
+ print('The current file does not exist: {}'.format(opts.current), file=sys.stderr)
362
+ sys.exit(1)
363
+
364
+
365
+ # Guess the test name from the current file
366
+ if opts.test:
367
+ main_test_name = opts.test
368
+ else:
369
+ main_test_name = os.path.splitext(os.path.basename(opts.current))[0]
370
+
371
+ if opts.reconcile:
372
+ main_reconcile = opts.reconcile
373
+ else:
374
+ main_reconcile = os.environ.get('BOND_RECONCILE', 'console')
375
+
376
+ main_settings = dict(reconcile=main_reconcile)
377
+
378
+ if reconcile_observations(main_settings,
379
+ main_test_name,
380
+ opts.reference,
381
+ opts.current,
382
+ no_save=opts.no_save):
383
+ sys.exit(0)
384
+ else:
385
+ sys.exit(1)