comparison tests/test_processing_base.py @ 120:133845647083

Better error management and removal support in baking/processing. * Baker and processor pipeline now store errors in their records. * They also support deleting output files that are no longer valid. * The basic transitional record class implements more boilerplate code. * The processor pipeline is run from the `bake` command directly. * New unit tests. * Unit test mocking now mocks `os.remove` too.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 09 Nov 2014 14:46:23 -0800
parents 3471ffa059b2
children e725af1d48fb
comparison
equal deleted inserted replaced
119:0811f92cbdc7 120:133845647083
1 import time
1 import os.path 2 import os.path
3 import shutil
2 import pytest 4 import pytest
3 from piecrust.processing.base import ProcessorPipeline 5 from piecrust.processing.base import (ProcessorPipeline, SimpleFileProcessor)
6 from piecrust.processing.records import ProcessorPipelineRecord
4 from .mockutil import mock_fs, mock_fs_scope 7 from .mockutil import mock_fs, mock_fs_scope
5 8
6 9
7 def _get_pipeline(fs, **kwargs): 10 class FooProcessor(SimpleFileProcessor):
8 app = fs.getApp(cache=False) 11 def __init__(self, exts=None, open_func=None):
12 exts = exts or {'foo', 'foo'}
13 super(FooProcessor, self).__init__({exts[0]: exts[1]})
14 self.PROCESSOR_NAME = exts[0]
15 self.open_func = open_func or open
16
17 def _doProcess(self, in_path, out_path):
18 with self.open_func(in_path, 'r') as f:
19 text = f.read()
20 with self.open_func(out_path, 'w') as f:
21 f.write("%s: %s" % (self.PROCESSOR_NAME.upper(), text))
22 return True
23
24
25 class NoopProcessor(SimpleFileProcessor):
26 def __init__(self, exts):
27 super(NoopProcessor, self).__init__({exts[0]: exts[1]})
28 self.PROCESSOR_NAME = exts[0]
29 self.processed = []
30
31 def _doProcess(self, in_path, out_path):
32 self.processed.append(in_path)
33 shutil.copyfile(in_path, out_path)
34 return True
35
36
37 def _get_pipeline(fs, cache=True, **kwargs):
38 app = fs.getApp(cache=cache)
9 mounts = [os.path.join(app.root_dir, 'assets')] 39 mounts = [os.path.join(app.root_dir, 'assets')]
10 return ProcessorPipeline(app, mounts, fs.path('counter'), 40 return ProcessorPipeline(app, mounts, fs.path('counter'),
11 num_workers=1, **kwargs) 41 num_workers=1, **kwargs)
12 42
13 43
34 pp.run() 64 pp.run()
35 expected = {'something.html': 'A test file.'} 65 expected = {'something.html': 'A test file.'}
36 assert expected == fs.getStructure('counter') 66 assert expected == fs.getStructure('counter')
37 67
38 68
69 def test_one_level_dirtyness():
70 fs = (mock_fs()
71 .withFile('kitchen/assets/blah.foo', 'A test file.'))
72 with mock_fs_scope(fs):
73 pp = _get_pipeline(fs)
74 pp.filterProcessors(['copy'])
75 pp.run()
76 expected = {'blah.foo': 'A test file.'}
77 assert expected == fs.getStructure('counter')
78 mtime = os.path.getmtime(fs.path('/counter/blah.foo'))
79 assert abs(time.time() - mtime) <= 2
80
81 pp.run()
82 assert expected == fs.getStructure('counter')
83 assert mtime == os.path.getmtime(fs.path('/counter/blah.foo'))
84
85 fs.withFile('kitchen/assets/blah.foo', 'A new test file.')
86 pp.run()
87 expected = {'blah.foo': 'A new test file.'}
88 assert expected == fs.getStructure('counter')
89 assert mtime < os.path.getmtime(fs.path('/counter/blah.foo'))
90
91
92 def test_two_levels_dirtyness():
93 fs = (mock_fs()
94 .withFile('kitchen/assets/blah.foo', 'A test file.'))
95 with mock_fs_scope(fs) as scope:
96 pp = _get_pipeline(fs)
97 pp.processors.append(FooProcessor(('foo', 'bar'), scope._open))
98 pp.filterProcessors(['foo', 'copy'])
99 pp.run()
100 expected = {'blah.bar': 'FOO: A test file.'}
101 assert expected == fs.getStructure('counter')
102 mtime = os.path.getmtime(fs.path('/counter/blah.bar'))
103 assert abs(time.time() - mtime) <= 2
104
105 pp.run()
106 assert expected == fs.getStructure('counter')
107 assert mtime == os.path.getmtime(fs.path('/counter/blah.bar'))
108
109 fs.withFile('kitchen/assets/blah.foo', 'A new test file.')
110 pp.run()
111 expected = {'blah.bar': 'FOO: A new test file.'}
112 assert expected == fs.getStructure('counter')
113 assert mtime < os.path.getmtime(fs.path('/counter/blah.bar'))
114
115
116 def test_removed():
117 fs = (mock_fs()
118 .withFile('kitchen/assets/blah1.foo', 'A test file.')
119 .withFile('kitchen/assets/blah2.foo', 'Ooops'))
120 with mock_fs_scope(fs):
121 expected = {
122 'blah1.foo': 'A test file.',
123 'blah2.foo': 'Ooops'}
124 assert expected == fs.getStructure('kitchen/assets')
125 pp = _get_pipeline(fs)
126 pp.filterProcessors(['copy'])
127 pp.run()
128 assert expected == fs.getStructure('counter')
129
130 os.remove(fs.path('/kitchen/assets/blah2.foo'))
131 expected = {
132 'blah1.foo': 'A test file.'}
133 assert expected == fs.getStructure('kitchen/assets')
134 pp.run()
135 assert expected == fs.getStructure('counter')
136
137
138 def test_record_version_change():
139 fs = (mock_fs()
140 .withFile('kitchen/assets/blah.foo', 'A test file.'))
141 with mock_fs_scope(fs):
142 pp = _get_pipeline(fs)
143 noop = NoopProcessor(('foo', 'foo'))
144 pp.processors.append(noop)
145 pp.filterProcessors(['foo', 'copy'])
146 pp.run()
147 assert 1 == len(noop.processed)
148
149 pp.run()
150 assert 1 == len(noop.processed)
151
152 ProcessorPipelineRecord.RECORD_VERSION += 1
153 try:
154 pp.run()
155 assert 2 == len(noop.processed)
156 finally:
157 ProcessorPipelineRecord.RECORD_VERSION -= 1
158
159
39 @pytest.mark.parametrize('patterns, expected', [ 160 @pytest.mark.parametrize('patterns, expected', [
40 (['_'], 161 (['_'],
41 {'something.html': 'A test file.'}), 162 {'something.html': 'A test file.'}),
42 (['html'], 163 (['html'],
43 {}), 164 {}),