Skip to content

Commit a16a2b7

Browse files
committed
Add: testsuite for backend build scheduling routines
1 parent 71c7b94 commit a16a2b7

File tree

4 files changed

+357
-2
lines changed

4 files changed

+357
-2
lines changed

fpm/src/fpm_model.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ module fpm_model
8383
logical :: skip = .false.
8484
! Flag set if build target will be skipped (not built)
8585

86-
integer :: schedule
86+
integer :: schedule = -1
8787
! Targets in the same schedule group are guaranteed to be independent
8888
integer(int64), allocatable :: digest_cached
8989
! Previous hash

fpm/test/fpm_test/main.f90

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ program fpm_testing
77
use test_manifest, only : collect_manifest
88
use test_source_parsing, only : collect_source_parsing
99
use test_module_dependencies, only : collect_module_dependencies
10+
use test_backend, only: collect_backend
1011
use test_versioning, only : collect_versioning
1112
implicit none
1213
integer :: stat, is
@@ -21,6 +22,7 @@ program fpm_testing
2122
& new_testsuite("fpm_manifest", collect_manifest), &
2223
& new_testsuite("fpm_source_parsing", collect_source_parsing), &
2324
& new_testsuite("fpm_module_dependencies", collect_module_dependencies), &
25+
& new_testsuite("fpm_test_backend", collect_backend), &
2426
& new_testsuite("fpm_versioning", collect_versioning) &
2527
& ]
2628

fpm/test/fpm_test/test_backend.f90

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
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

fpm/test/fpm_test/test_module_dependencies.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module test_module_dependencies
1212
implicit none
1313
private
1414

15-
public :: collect_module_dependencies
15+
public :: collect_module_dependencies, operator(.in.)
1616

1717
interface operator(.in.)
1818
module procedure target_in

0 commit comments

Comments
 (0)