1
+ ! > Define tests for the `fpm_backend` module (build scheduling)
2
+ module test_backend
3
+ use testsuite, only : new_unittest, unittest_t, error_t, test_failed
4
+ use test_module_dependencies, only: operator (.in .)
5
+ use fpm_filesystem, only: exists, mkdir, get_temp_filename
6
+ use fpm_model, only: build_target_t, build_target_ptr, &
7
+ FPM_TARGET_OBJECT, FPM_TARGET_ARCHIVE
8
+ use fpm_targets, only: add_target, add_dependency
9
+ use fpm_backend, only: sort_target, schedule_targets
10
+ implicit none
11
+ private
12
+
13
+ public :: collect_backend
14
+
15
+ contains
16
+
17
+
18
+ ! > Collect all exported unit tests
19
+ subroutine collect_backend (testsuite )
20
+
21
+ ! > Collection of tests
22
+ type (unittest_t), allocatable , intent (out ) :: testsuite(:)
23
+
24
+ testsuite = [ &
25
+ & new_unittest(" target-sort" , test_target_sort), &
26
+ & new_unittest(" target-sort-skip-all" , test_target_sort_skip_all), &
27
+ & new_unittest(" target-sort-rebuild-all" , test_target_sort_rebuild_all), &
28
+ & new_unittest(" schedule-targets" , test_schedule_targets), &
29
+ & new_unittest(" schedule-targets-empty" , test_schedule_empty) &
30
+ ]
31
+
32
+ end subroutine collect_backend
33
+
34
+
35
+ ! > Check scheduling of objects with dependencies
36
+ subroutine test_target_sort (error )
37
+
38
+ ! > Error handling
39
+ type (error_t), allocatable , intent (out ) :: error
40
+
41
+ type (build_target_ptr), allocatable :: targets(:)
42
+
43
+ integer :: i
44
+
45
+ targets = new_test_package()
46
+
47
+ ! Perform depth-first topological sort of targets
48
+ do i= 1 ,size (targets)
49
+
50
+ call sort_target(targets(i)% ptr)
51
+
52
+ end do
53
+
54
+ ! Check target states: all targets scheduled
55
+ do i= 1 ,size (targets)
56
+
57
+ if (.not. targets(i)% ptr% touched) then
58
+ call test_failed(error," Target touched flag not set" )
59
+ return
60
+ end if
61
+
62
+ if (.not. targets(i)% ptr% sorted) then
63
+ call test_failed(error," Target sort flag not set" )
64
+ return
65
+ end if
66
+
67
+ if (targets(i)% ptr% skip) then
68
+ call test_failed(error," Target skip flag set incorrectly" )
69
+ return
70
+ end if
71
+
72
+ if (targets(i)% ptr% schedule < 0 ) then
73
+ call test_failed(error," Target schedule not set" )
74
+ return
75
+ end if
76
+
77
+ end do
78
+
79
+ ! Check all objects sheduled before library
80
+ do i= 2 ,size (targets)
81
+
82
+ if (targets(i)% ptr% schedule >= targets(1 )% ptr% schedule) then
83
+ call test_failed(error," Object dependency scheduled after dependent library target" )
84
+ return
85
+ end if
86
+
87
+ end do
88
+
89
+ ! Check target 4 schedule before targets 2 & 3
90
+ do i= 2 ,3
91
+ if (targets(4 )% ptr% schedule >= targets(i)% ptr% schedule) then
92
+ call test_failed(error," Object dependency scheduled after dependent object target" )
93
+ return
94
+ end if
95
+ end do
96
+
97
+ end subroutine test_target_sort
98
+
99
+
100
+
101
+ ! > Check incremental rebuild for existing archive
102
+ ! > all object sources are unmodified: all objects should be skipped
103
+ subroutine test_target_sort_skip_all (error )
104
+
105
+ ! > Error handling
106
+ type (error_t), allocatable , intent (out ) :: error
107
+
108
+ type (build_target_ptr), allocatable :: targets(:)
109
+
110
+ integer :: fh, i
111
+
112
+ targets = new_test_package()
113
+
114
+ do i= 2 ,size (targets)
115
+
116
+ ! Mimick unmodified sources
117
+ allocate (targets(i)% ptr% source)
118
+ targets(i)% ptr% source% digest = i
119
+ targets(i)% ptr% digest_cached = i
120
+
121
+ end do
122
+
123
+ ! Mimick archive already exists
124
+ open (newunit= fh,file= targets(1 )% ptr% output_file,status= " unknown" )
125
+ close (fh)
126
+
127
+ ! Perform depth-first topological sort of targets
128
+ do i= 1 ,size (targets)
129
+
130
+ call sort_target(targets(i)% ptr)
131
+
132
+ end do
133
+
134
+ ! Check target states: all targets skipped
135
+ do i= 1 ,size (targets)
136
+
137
+ if (.not. targets(i)% ptr% touched) then
138
+ call test_failed(error," Target touched flag not set" )
139
+ return
140
+ end if
141
+
142
+ if (targets(i)% ptr% sorted) then
143
+ call test_failed(error," Target sort flag set incorrectly" )
144
+ return
145
+ end if
146
+
147
+ if (.not. targets(i)% ptr% skip) then
148
+ call test_failed(error," Target skip flag set incorrectly" )
149
+ return
150
+ end if
151
+
152
+ end do
153
+
154
+ end subroutine test_target_sort_skip_all
155
+
156
+
157
+ ! > Check incremental rebuild for existing archive
158
+ ! > all but lowest source modified: all objects should be rebuilt
159
+ subroutine test_target_sort_rebuild_all (error )
160
+
161
+ ! > Error handling
162
+ type (error_t), allocatable , intent (out ) :: error
163
+
164
+ type (build_target_ptr), allocatable :: targets(:)
165
+
166
+ integer :: fh, i
167
+
168
+ targets = new_test_package()
169
+
170
+ do i= 2 ,3
171
+
172
+ ! Mimick unmodified sources
173
+ allocate (targets(i)% ptr% source)
174
+ targets(i)% ptr% source% digest = i
175
+ targets(i)% ptr% digest_cached = i
176
+
177
+ end do
178
+
179
+ ! Mimick archive already exists
180
+ open (newunit= fh,file= targets(1 )% ptr% output_file,status= " unknown" )
181
+ close (fh)
182
+
183
+ ! Perform depth-first topological sort of targets
184
+ do i= 1 ,size (targets)
185
+
186
+ call sort_target(targets(i)% ptr)
187
+
188
+ end do
189
+
190
+ ! Check target states: all targets scheduled
191
+ do i= 1 ,size (targets)
192
+
193
+ if (.not. targets(i)% ptr% sorted) then
194
+ call test_failed(error," Target sort flag not set" )
195
+ return
196
+ end if
197
+
198
+ if (targets(i)% ptr% skip) then
199
+ call test_failed(error," Target skip flag set incorrectly" )
200
+ return
201
+ end if
202
+
203
+ end do
204
+
205
+ end subroutine test_target_sort_rebuild_all
206
+
207
+
208
+ ! > Check construction of target queue and schedule
209
+ subroutine test_schedule_targets (error )
210
+
211
+ ! > Error handling
212
+ type (error_t), allocatable , intent (out ) :: error
213
+
214
+ type (build_target_ptr), allocatable :: targets(:)
215
+
216
+ integer :: i, j
217
+ type (build_target_ptr), allocatable :: queue(:)
218
+ integer , allocatable :: schedule_ptr(:)
219
+
220
+ targets = new_test_package()
221
+
222
+ ! Perform depth-first topological sort of targets
223
+ do i= 1 ,size (targets)
224
+
225
+ call sort_target(targets(i)% ptr)
226
+
227
+ end do
228
+
229
+ ! Construct build schedule queue
230
+ call schedule_targets(queue, schedule_ptr, targets)
231
+
232
+ ! Check all targets enqueued
233
+ do i= 1 ,size (targets)
234
+
235
+ if (.not. (targets(i)% ptr.in .queue)) then
236
+
237
+ call test_failed(error," Target not found in build queue" )
238
+ return
239
+
240
+ end if
241
+
242
+ end do
243
+
244
+ ! Check schedule structure
245
+ if (schedule_ptr(1 ) /= 1 ) then
246
+
247
+ call test_failed(error," schedule_ptr(1) does not point to start of the queue" )
248
+ return
249
+
250
+ end if
251
+
252
+ if (schedule_ptr(size (schedule_ptr)) /= size (queue)+ 1 ) then
253
+
254
+ call test_failed(error," schedule_ptr(end) does not point to end of the queue" )
255
+ return
256
+
257
+ end if
258
+
259
+ do i= 1 ,size (schedule_ptr)- 1
260
+
261
+ do j= schedule_ptr(i),(schedule_ptr(i+1 )- 1 )
262
+
263
+ if (queue(j)% ptr% schedule /= i) then
264
+
265
+ call test_failed(error," Target scheduled in the wrong region" )
266
+ return
267
+
268
+ end if
269
+
270
+ end do
271
+
272
+ end do
273
+
274
+ end subroutine test_schedule_targets
275
+
276
+
277
+ ! > Check construction of target queue and schedule
278
+ ! > when there's nothing to do (all targets skipped)
279
+ subroutine test_schedule_empty (error )
280
+
281
+ ! > Error handling
282
+ type (error_t), allocatable , intent (out ) :: error
283
+
284
+ type (build_target_ptr), allocatable :: targets(:)
285
+
286
+ integer :: i
287
+ type (build_target_ptr), allocatable :: queue(:)
288
+ integer , allocatable :: schedule_ptr(:)
289
+
290
+ targets = new_test_package()
291
+
292
+ do i= 1 ,size (targets)
293
+
294
+ targets(i)% ptr% skip = .true.
295
+
296
+ end do
297
+
298
+ ! Perform depth-first topological sort of targets
299
+ do i= 1 ,size (targets)
300
+
301
+ call sort_target(targets(i)% ptr)
302
+
303
+ end do
304
+
305
+ ! Construct build schedule queue
306
+ call schedule_targets(queue, schedule_ptr, targets)
307
+
308
+ ! Check queue is empty
309
+ if (size (queue) > 0 ) then
310
+
311
+ call test_failed(error," Expecting an empty build queue, but not empty" )
312
+ return
313
+
314
+ end if
315
+
316
+ ! Check schedule loop is not entered
317
+ do i= 1 ,size (schedule_ptr)- 1
318
+
319
+ call test_failed(error," Attempted to run an empty schedule" )
320
+ return
321
+
322
+ end do
323
+
324
+ end subroutine test_schedule_empty
325
+
326
+
327
+ ! > Helper to generate target objects with dependencies
328
+ function new_test_package () result(targets)
329
+
330
+ type (build_target_ptr), allocatable :: targets(:)
331
+
332
+ call add_target(targets,FPM_TARGET_ARCHIVE,get_temp_filename())
333
+
334
+ call add_target(targets,FPM_TARGET_OBJECT,get_temp_filename())
335
+
336
+ call add_target(targets,FPM_TARGET_OBJECT,get_temp_filename())
337
+
338
+ call add_target(targets,FPM_TARGET_OBJECT,get_temp_filename())
339
+
340
+ ! Library depends on all objects
341
+ call add_dependency(targets(1 )% ptr,targets(2 )% ptr)
342
+ call add_dependency(targets(1 )% ptr,targets(3 )% ptr)
343
+ call add_dependency(targets(1 )% ptr,targets(4 )% ptr)
344
+
345
+ ! Inter-object dependency
346
+ ! targets 2 & 3 depend on target 4
347
+ call add_dependency(targets(2 )% ptr,targets(4 )% ptr)
348
+ call add_dependency(targets(3 )% ptr,targets(4 )% ptr)
349
+
350
+ end function new_test_package
351
+
352
+
353
+ end module test_backend
0 commit comments