bond-spy 0.1.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.
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)