build-graph 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env rspec
2
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require 'build/graph/call_stack'
23
+
24
+ module Build::Graph::CallStackSpec
25
+ describe Build::Graph::CallStack do
26
+ it "should merge state" do
27
+ outer_state = nil
28
+ inner_state = nil
29
+
30
+ subject.with(x: 10) do
31
+ outer_state = subject.last
32
+
33
+ subject.with(x: 20, y: 30) do
34
+ inner_state = subject.last
35
+ end
36
+ end
37
+
38
+ expect(outer_state).to include(:x)
39
+ expect(inner_state).to include(:x, :y)
40
+ end
41
+ end
42
+ end
@@ -1,4 +1,6 @@
1
- # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
1
+ #!/usr/bin/env rspec
2
+
3
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
6
  # of this software and associated documentation files (the "Software"), to deal
@@ -18,21 +20,20 @@
18
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
21
  # THE SOFTWARE.
20
22
 
21
- module Build
22
- module Graph
23
- class TransientError < StandardError
24
- end
23
+ require_relative 'process_graph'
24
+
25
+ RSpec.describe Build::Graph::Edge do
26
+ let(:failed_task) {double(:failed? => true)}
25
27
 
26
- class CommandFailure < TransientError
27
- def initialize(command, status)
28
- super "Command #{command.inspect} failed with exit status #{status}!"
28
+ it "should fail if failed task is added" do
29
+ subject.traverse(failed_task)
29
30
 
30
- @command = command
31
- @status = status
32
- end
31
+ expect(subject.failed?).to be_truthy
32
+ end
33
33
 
34
- attr :command
35
- attr :status
36
- end
34
+ it "should fail if failed task is skipped" do
35
+ subject.skip!(failed_task)
36
+
37
+ expect(subject.failed?).to be_truthy
37
38
  end
38
39
  end
@@ -25,13 +25,16 @@ module Build::Graph::GraphSpec
25
25
  include ProcessGraph
26
26
 
27
27
  describe Build::Graph do
28
+ let(:group) {Process::Group.new}
29
+
30
+ let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
31
+
28
32
  it "shouldn't update mtime" do
29
33
  test_glob = Glob.new(__dir__, "*.rb")
30
34
  listing_output = Paths.directory(__dir__, ["listing.txt"])
31
35
 
32
36
  FileUtils.rm_f listing_output.to_a
33
37
 
34
- group = Process::Group.new
35
38
  walker = Walker.for(ProcessTask, group)
36
39
 
37
40
  top = ProcessNode.top do
@@ -40,13 +43,15 @@ module Build::Graph::GraphSpec
40
43
  end
41
44
  end
42
45
 
43
- walker.update(top)
44
- group.wait
46
+ group.wait do
47
+ walker.update(top)
48
+ end
45
49
 
46
50
  first_modified_time = listing_output.first.modified_time
47
51
 
48
- walker.update(top)
49
- group.wait
52
+ group.wait do
53
+ walker.update(top)
54
+ end
50
55
 
51
56
  # The output file shouldn't have been changed because already exists and the input files haven't changed either:
52
57
  second_modified_time = listing_output.first.modified_time
@@ -60,8 +65,9 @@ module Build::Graph::GraphSpec
60
65
  # The granularity of modification times isn't that great, so we use >= below.
61
66
  # sleep 1
62
67
 
63
- walker.update(top)
64
- group.wait
68
+ group.wait do
69
+ walker.update(top)
70
+ end
65
71
 
66
72
  expect(listing_output.first.modified_time).to be >= first_modified_time
67
73
 
@@ -73,7 +79,6 @@ module Build::Graph::GraphSpec
73
79
  code_glob = Glob.new(program_root, "*.cpp")
74
80
  program_path = Path.join(program_root, "dictionary-sort")
75
81
 
76
- group = Process::Group.new
77
82
  walker = Walker.for(ProcessTask, group)
78
83
 
79
84
  #FileUtils.touch(code_glob.first)
@@ -110,8 +115,9 @@ module Build::Graph::GraphSpec
110
115
  end
111
116
  end
112
117
 
113
- walker.update(top)
114
- group.wait
118
+ group.wait do
119
+ walker.update(top)
120
+ end
115
121
 
116
122
  expect(program_path).to be_exist
117
123
  expect(code_glob.first.modified_time).to be <= program_path.modified_time
@@ -122,48 +128,48 @@ module Build::Graph::GraphSpec
122
128
  files = Glob.new(program_root, "*.cpp")
123
129
  destination = Path.new(__dir__) + "tmp"
124
130
 
125
- group = Process::Group.new
126
131
  walker = Walker.for(ProcessTask, group)
127
132
 
128
- #FileUtils.touch(code_glob.first)
129
-
130
133
  top = ProcessNode.top files do
131
- fs.mkpath destination
134
+ mkpath destination
132
135
 
133
136
  inputs.each do |source_path|
134
137
  destination_path = source_path.rebase(destination)
135
138
 
136
139
  process source_path, destination_path do
137
- fs.install inputs.first, outputs.first
140
+ $stderr.puts "Copying #{inputs.first} -> #{outputs.first}"
141
+ install inputs.first, outputs.first
138
142
  end
139
143
  end
140
144
  end
141
145
 
142
- triggered = 0
143
- trashed_files = false
146
+ mutex = Mutex.new
147
+ files_deleted = false
144
148
 
145
149
  thread = Thread.new do
146
- while triggered == 0 or trashed_files == false
147
- sleep 0.1 if trashed_files
148
-
149
- destination.glob("*.cpp").each{|path| path.delete}
150
+ sleep 1
151
+
152
+ mutex.synchronize do
153
+ destination.glob("*.cpp").delete
150
154
 
151
- trashed_files = true
155
+ files_deleted = true
152
156
  end
153
157
  end
154
158
 
155
159
  walker.run do
156
- triggered += 1
157
-
158
- walker.update(top)
159
- group.wait
160
+ mutex.synchronize do
161
+ group.wait do
162
+ walker.update(top)
163
+ end
164
+ end
160
165
 
161
- break if trashed_files
166
+ break if files_deleted
162
167
  end
163
168
 
164
169
  thread.join
165
170
 
166
171
  expect(destination).to be_exist
172
+ # This line failed, may still be a race condition:
167
173
  expect(destination.glob("*.cpp").count).to be == 2
168
174
 
169
175
  destination.delete
@@ -58,12 +58,16 @@ module ProcessGraph
58
58
  end
59
59
  end
60
60
 
61
- def fs
62
- if wet?
63
- FileUtils::Verbose
64
- else
65
- FileUtils::Verbose::Dry
66
- end
61
+ def mkpath(*args)
62
+ return unless wet?
63
+
64
+ FileUtils.mkpath(*args)
65
+ end
66
+
67
+ def install(*args)
68
+ return unless wet?
69
+
70
+ FileUtils.install(*args)
67
71
  end
68
72
 
69
73
  # This function is called to finish the invocation of the task within the graph.
@@ -18,16 +18,16 @@ namespace Benchmark
18
18
  gettimeofday (&t, (struct timezone*)0);
19
19
  return ((TimeT)t.tv_sec) + ((TimeT)t.tv_usec / 1000000.0);
20
20
  }
21
-
21
+
22
22
  WallTime::WallTime () {
23
23
  this->reset();
24
24
  }
25
-
25
+
26
26
  void WallTime::reset () {
27
27
  this->_last = system_time();
28
28
  this->_total = 0.0;
29
29
  }
30
-
30
+
31
31
  TimeT WallTime::total () const {
32
32
  TimeT current = system_time();
33
33
  this->_total += current - this->_last;
@@ -28,20 +28,20 @@ namespace DictionarySort
28
28
  //const int SORT_MODE = -1;
29
29
  // Use ParallelMergeSort with 2^n threads
30
30
  const int SORT_MODE = 3; // = n
31
-
31
+
32
32
  typedef std::uint64_t IndexT;
33
-
33
+
34
34
  template <typename CharT, typename MapT>
35
35
  class Dictionary {
36
36
  public:
37
37
  typedef std::vector<CharT> WordT;
38
38
  typedef std::vector<WordT> WordsT;
39
39
  typedef std::vector<IndexT> OrderT;
40
-
40
+
41
41
  static const int ORDERED_LT = -1;
42
42
  static const int ORDERED_EQ = 0;
43
43
  static const int ORDERED_GT = 1;
44
-
44
+
45
45
  // Compare two order vectors to determine the relative nature of lhs compared to rhs.
46
46
  // We assume that lhs and rhs have at least one element each.
47
47
  // ORDERED_LT => (lhs < rhs)
@@ -50,155 +50,155 @@ namespace DictionarySort
50
50
  static int compare(const OrderT & lhs, const OrderT & rhs)
51
51
  {
52
52
  std::size_t offset = 0;
53
-
53
+
54
54
  while (offset < lhs.size() && offset < rhs.size()) {
55
55
  if (lhs[offset] < rhs[offset])
56
56
  return ORDERED_LT;
57
57
  else if (lhs[offset] > rhs[offset])
58
58
  return ORDERED_GT;
59
-
59
+
60
60
  offset += 1;
61
61
  }
62
-
62
+
63
63
  if (lhs.size() == rhs.size())
64
64
  return ORDERED_EQ;
65
-
66
- // lhs was longer,
65
+
66
+ // lhs was longer,
67
67
  if (offset < lhs.size())
68
68
  return ORDERED_GT;
69
-
69
+
70
70
  return ORDERED_LT;
71
71
  }
72
-
72
+
73
73
  static int compare(Dictionary * dictionary, const WordT & lhs, const WordT & rhs) {
74
74
  std::size_t offset = 0;
75
-
75
+
76
76
  while (offset < lhs.size() && offset < rhs.size()) {
77
77
  IndexT left_order = dictionary->_characterOrder[lhs[offset]];
78
78
  IndexT right_order = dictionary->_characterOrder[rhs[offset]];
79
-
79
+
80
80
  if (left_order < right_order)
81
81
  return ORDERED_LT;
82
82
  else if (left_order > right_order)
83
83
  return ORDERED_GT;
84
-
84
+
85
85
  offset += 1;
86
86
  }
87
-
87
+
88
88
  if (lhs.size() == rhs.size())
89
89
  return ORDERED_EQ;
90
-
90
+
91
91
  if (offset < lhs.size())
92
92
  return ORDERED_GT;
93
-
93
+
94
94
  return ORDERED_LT;
95
95
  }
96
-
96
+
97
97
  private:
98
98
  WordT _alphabet;
99
-
99
+
100
100
  MapT _characterOrder;
101
101
  //int _characterOrder[256];
102
102
  //std::map<CharT, IndexT> _characterOrder;
103
-
103
+
104
104
  IndexT width;
105
105
  IndexT characters_per_segment;
106
-
106
+
107
107
  // This is a light weight wrapper over WordT which caches its OrderT, an integer representation of position based on the given dictionary.
108
108
  struct OrderedWord {
109
109
  WordT word;
110
-
110
+
111
111
  // We can generate this as part of the sorting process. Because the sort is parallel, generation of word order (which is relatively expensive) is distributed across multiple processors.
112
112
  mutable OrderT order;
113
-
113
+
114
114
  // The first time this function is called, it must be guaranteed from a single thread.
115
115
  // After that, it can be called from multiple threads at the same time.
116
116
  // The parallel merge sort algorithm guarantees this.
117
117
  const OrderT & fetch_order(Dictionary * dictionary) const {
118
118
  if (order.size() == 0 && word.size() > 0)
119
119
  order = dictionary->sum(word);
120
-
120
+
121
121
  return order;
122
122
  }
123
123
  };
124
-
124
+
125
125
  struct CompareWordsAscending {
126
126
  Dictionary * dictionary;
127
-
127
+
128
128
  CompareWordsAscending (Dictionary * _dictionary)
129
129
  : dictionary(_dictionary)
130
130
  {
131
131
  }
132
-
132
+
133
133
  bool operator()(const WordT * a, const WordT * b) const {
134
134
  return compare(dictionary, a, b) == ORDERED_LT;
135
135
  }
136
-
136
+
137
137
  bool operator()(const OrderedWord * a, const OrderedWord * b) const {
138
138
  return compare(a->fetch_order(dictionary), b->fetch_order(dictionary)) == ORDERED_LT;
139
139
  }
140
140
  };
141
-
141
+
142
142
  struct UnorderedWord {
143
143
  WordT word;
144
144
  Dictionary * dictionary;
145
-
145
+
146
146
  UnorderedWord(const WordT & _word, Dictionary * _dictionary)
147
147
  : word(_word), dictionary(_dictionary) {
148
-
148
+
149
149
  }
150
-
150
+
151
151
  bool operator<(const UnorderedWord & other) const {
152
152
  return compare(dictionary, *this, other) == ORDERED_LT;
153
153
  }
154
154
  };
155
-
155
+
156
156
  public:
157
157
  Dictionary(WordT alphabet)
158
158
  : _alphabet(alphabet)
159
159
  {
160
160
  IndexT index = 1;
161
-
161
+
162
162
  // Build up the character order map
163
163
  for (typename WordT::iterator i = _alphabet.begin(); i != _alphabet.end(); ++i) {
164
164
  _characterOrder[*i] = index;
165
165
  index += 1;
166
166
  }
167
-
167
+
168
168
  width = std::ceil(std::log(alphabet.size()) / std::log(2));
169
-
169
+
170
170
  // Naturally floor the result by integer division/truncation.
171
171
  characters_per_segment = (sizeof(IndexT) * 8) / width;
172
172
  }
173
-
173
+
174
174
  OrderT sum(const WordT & word) {
175
175
  OrderT order;
176
176
  std::size_t index = 0;
177
-
177
+
178
178
  while (index < word.size()) {
179
179
  IndexT count = characters_per_segment;
180
180
  IndexT sum = 0;
181
-
181
+
182
182
  while (index < word.size()) {
183
183
  count -= 1;
184
-
184
+
185
185
  sum <<= width;
186
186
  sum += _characterOrder[word[index]];
187
-
187
+
188
188
  index += 1;
189
-
189
+
190
190
  if (count == 0)
191
191
  break;
192
192
  }
193
-
193
+
194
194
  // Shift along any remaining count, since we are ordering using the left most significant character.
195
195
  sum <<= (count * width);
196
196
  order.push_back(sum);
197
197
  }
198
-
198
+
199
199
  return order;
200
200
  }
201
-
201
+
202
202
  // The words will be sorted in-place.
203
203
  template <typename ToSortT>
204
204
  void sort (ToSortT & words, int mode = 2)
@@ -210,7 +210,7 @@ namespace DictionarySort
210
210
  if (mode == -1) {
211
211
  // Sort the words using built-in sorting algorithm, for comparison:
212
212
  std::sort(words.begin(), words.end(), comparator);
213
- } else {
213
+ } else {
214
214
  ParallelMergeSort::sort(words, comparator, std::size_t(mode));
215
215
  }
216
216
 
@@ -221,7 +221,7 @@ namespace DictionarySort
221
221
  std::cerr << " * Processor sort time: " << sample.processor_time_total << std::endl;
222
222
  std::cerr << " * Approximate processor usage: " << sample.approximate_processor_usage() << std::endl;
223
223
  }
224
-
224
+
225
225
  // This function can be slow due to the large amount of memory required for large datasets.
226
226
  uint64_t sort(const WordsT & input, WordsT & output)
227
227
  {
@@ -229,41 +229,41 @@ namespace DictionarySort
229
229
 
230
230
  // Allocate all words in one go:
231
231
  OrderedWord * allocation = new OrderedWord[input.size()];
232
-
232
+
233
233
  // Copy pointers to intermediate list which will be used for sorting:
234
234
  OrderedWordsT words(input.size());
235
-
235
+
236
236
  // Calculate order vector for each word in preparation for sort.
237
237
  for (std::size_t i = 0; i < input.size(); i += 1) {
238
238
  words[i] = &allocation[i];
239
-
239
+
240
240
  words[i]->word = input[i];
241
-
241
+
242
242
  // We can force generation of the order cache, but performance may be reduced by about 10%.
243
243
  //words[i]->fetch_order(this);
244
244
  }
245
-
245
+
246
246
  // Change the mode from -1 for std::sort, to 0..n for ParallelMergeSort where 2^n is the number of threads to use.
247
247
  sort(words, SORT_MODE);
248
-
248
+
249
249
  // Prepare container for sorted output:
250
250
  output.reserve(input.size());
251
251
  output.resize(0);
252
-
252
+
253
253
  uint64_t checksum = 1, offset = 1;
254
254
  // Copy sorted words to output vector.
255
255
  for (typename OrderedWordsT::iterator i = words.begin(); i != words.end(); ++i) {
256
256
  output.push_back((*i)->word);
257
-
257
+
258
258
  // Compute a very simple checksum for verifying sorted order.
259
259
  const OrderT & order = (*i)->fetch_order(this);
260
260
  for (typename OrderT::const_iterator j = order.begin(); j != order.end(); ++j) {
261
261
  checksum ^= *j + (offset++ % checksum);
262
- }
262
+ }
263
263
  }
264
-
264
+
265
265
  delete[] allocation;
266
-
266
+
267
267
  return checksum;
268
268
  }
269
269
  };