@@ -9,9 +9,9 @@ use insta::{Settings, assert_snapshot};
99use netsuke:: ast:: Recipe ;
1010use netsuke:: ir:: { Action , BuildEdge , BuildGraph } ;
1111use netsuke:: ninja_gen:: generate;
12- use rstest:: rstest;
12+ use rstest:: { fixture , rstest} ;
1313use std:: { fs, path:: PathBuf , process:: Command } ;
14- use tempfile:: tempdir;
14+ use tempfile:: { TempDir , tempdir} ;
1515
1616fn skip_if_ninja_unavailable ( ) -> bool {
1717 match Command :: new ( "ninja" ) . arg ( "--version" ) . output ( ) {
@@ -27,8 +27,26 @@ fn skip_if_ninja_unavailable() -> bool {
2727 }
2828}
2929
30+ /// Define how the integration test should assert Ninja's behaviour.
31+ #[ derive( Debug ) ]
32+ enum AssertionType {
33+ FileContent ( String ) ,
34+ FileExists ,
35+ StatusSuccess ,
36+ }
37+
38+ /// Provide a temporary directory when Ninja is available, skipping otherwise.
39+ #[ fixture]
40+ fn ninja_integration_setup ( ) -> Option < TempDir > {
41+ if skip_if_ninja_unavailable ( ) {
42+ None
43+ } else {
44+ Some ( tempdir ( ) . expect ( "temp dir" ) )
45+ }
46+ }
47+
3048#[ rstest]
31- #[ case:: phony (
49+ #[ case:: phony_target_runs_command (
3250 Action {
3351 recipe: Recipe :: Command { command: "true" . into( ) } ,
3452 description: None ,
@@ -50,7 +68,7 @@ fn skip_if_ninja_unavailable() -> bool {
5068 concat!(
5169 "rule a\n " ,
5270 " command = true\n \n " ,
53- "build out: phony in\n \n " ,
71+ "build out: a in\n \n " ,
5472 ) ,
5573) ]
5674#[ case:: standard_build(
@@ -169,138 +187,137 @@ fn generate_multiline_script_snapshot() {
169187 ) ;
170188}
171189
172- /// Ensure a multi-line script produces a Ninja manifest that Ninja accepts.
173- #[ rstest]
174- #[ ignore = "requires Ninja" ]
175- fn integration_multiline_script_valid ( ) {
176- if skip_if_ninja_unavailable ( ) {
177- return ;
178- }
179-
180- let mut graph = BuildGraph :: default ( ) ;
181- graph. actions . insert (
182- "script" . into ( ) ,
183- Action {
184- recipe : Recipe :: Script {
185- script : "echo one\n echo two" . into ( ) ,
186- } ,
187- description : None ,
188- depfile : None ,
189- deps_format : None ,
190- pool : None ,
191- restat : false ,
192- } ,
193- ) ;
194- graph. targets . insert (
195- PathBuf :: from ( "out" ) ,
196- BuildEdge {
197- action_id : "script" . into ( ) ,
198- inputs : Vec :: new ( ) ,
199- explicit_outputs : vec ! [ PathBuf :: from( "out" ) ] ,
200- implicit_outputs : Vec :: new ( ) ,
201- order_only_deps : Vec :: new ( ) ,
202- phony : false ,
203- always : false ,
204- } ,
205- ) ;
206- graph. default_targets . push ( PathBuf :: from ( "out" ) ) ;
207-
208- let ninja = generate ( & graph) ;
209- let dir = tempdir ( ) . expect ( "temp dir" ) ;
210- fs:: write ( dir. path ( ) . join ( "build.ninja" ) , & ninja) . expect ( "write ninja" ) ;
211- let status = Command :: new ( "ninja" )
212- . arg ( "-n" )
213- . current_dir ( dir. path ( ) )
214- . status ( )
215- . expect ( "run ninja" ) ;
216- assert ! ( status. success( ) ) ;
217- }
218-
219- /// Test that scripts containing percent signs execute correctly.
190+ /// Integration scenarios to confirm Ninja executes commands correctly.
220191#[ rstest]
221- #[ ignore = "requires Ninja" ]
222- fn generate_script_with_percent ( ) {
223- if skip_if_ninja_unavailable ( ) {
224- return ;
225- }
226-
227- let action = Action {
228- recipe : Recipe :: Script {
229- script : "echo 100% > out" . into ( ) ,
230- } ,
192+ #[ case:: multiline_script_valid(
193+ Action {
194+ recipe: Recipe :: Script { script: "echo one\n echo two" . into( ) } ,
231195 description: None ,
232196 depfile: None ,
233197 deps_format: None ,
234198 pool: None ,
235199 restat: false ,
236- } ;
237- let edge = BuildEdge {
200+ } ,
201+ BuildEdge {
202+ action_id: "script" . into( ) ,
203+ inputs: Vec :: new( ) ,
204+ explicit_outputs: vec![ PathBuf :: from( "out" ) ] ,
205+ implicit_outputs: Vec :: new( ) ,
206+ order_only_deps: Vec :: new( ) ,
207+ phony: false ,
208+ always: false ,
209+ } ,
210+ PathBuf :: from( "out" ) ,
211+ vec![ "-n" ] ,
212+ AssertionType :: StatusSuccess ,
213+ ) ]
214+ #[ case:: script_with_percent(
215+ Action {
216+ recipe: Recipe :: Script { script: "echo 100% > out" . into( ) } ,
217+ description: None ,
218+ depfile: None ,
219+ deps_format: None ,
220+ pool: None ,
221+ restat: false ,
222+ } ,
223+ BuildEdge {
238224 action_id: "percent" . into( ) ,
239225 inputs: Vec :: new( ) ,
240226 explicit_outputs: vec![ PathBuf :: from( "out" ) ] ,
241227 implicit_outputs: Vec :: new( ) ,
242228 order_only_deps: Vec :: new( ) ,
243229 phony: false ,
244230 always: false ,
245- } ;
246- let mut graph = BuildGraph :: default ( ) ;
247- graph. actions . insert ( "percent" . into ( ) , action) ;
248- graph. targets . insert ( PathBuf :: from ( "out" ) , edge) ;
249- graph. default_targets . push ( PathBuf :: from ( "out" ) ) ;
250-
251- let ninja = generate ( & graph) ;
252- let dir = tempdir ( ) . expect ( "temp dir" ) ;
253- fs:: write ( dir. path ( ) . join ( "build.ninja" ) , & ninja) . expect ( "write ninja" ) ;
254- let status = Command :: new ( "ninja" )
255- . arg ( "out" )
256- . current_dir ( dir. path ( ) )
257- . status ( )
258- . expect ( "run ninja" ) ;
259- assert ! ( status. success( ) ) ;
260- let content = fs:: read_to_string ( dir. path ( ) . join ( "out" ) ) . expect ( "read out" ) ;
261- assert_eq ! ( content. trim( ) , "100%" ) ;
262- }
263-
264- #[ rstest]
265- #[ ignore = "requires Ninja" ]
266- fn generate_script_with_backtick ( ) {
267- if skip_if_ninja_unavailable ( ) {
268- return ;
269- }
270-
271- let action = Action {
272- recipe : Recipe :: Script {
273- script : "echo `echo hi` > out" . into ( ) ,
274- } ,
231+ } ,
232+ PathBuf :: from( "out" ) ,
233+ vec![ "out" ] ,
234+ AssertionType :: FileContent ( "100%" . into( ) ) ,
235+ ) ]
236+ #[ case:: script_with_backtick(
237+ Action {
238+ recipe: Recipe :: Script { script: "echo `echo hi` > out" . into( ) } ,
275239 description: None ,
276240 depfile: None ,
277241 deps_format: None ,
278242 pool: None ,
279243 restat: false ,
280- } ;
281- let edge = BuildEdge {
244+ } ,
245+ BuildEdge {
282246 action_id: "tick" . into( ) ,
283247 inputs: Vec :: new( ) ,
284248 explicit_outputs: vec![ PathBuf :: from( "out" ) ] ,
285249 implicit_outputs: Vec :: new( ) ,
286250 order_only_deps: Vec :: new( ) ,
287251 phony: false ,
288252 always: false ,
253+ } ,
254+ PathBuf :: from( "out" ) ,
255+ vec![ "out" ] ,
256+ AssertionType :: FileContent ( "hi" . into( ) ) ,
257+ ) ]
258+ #[ case:: phony_action_executes_command(
259+ Action {
260+ recipe: Recipe :: Command { command: "touch action-called.txt" . into( ) } ,
261+ description: None ,
262+ depfile: None ,
263+ deps_format: None ,
264+ pool: None ,
265+ restat: false ,
266+ } ,
267+ BuildEdge {
268+ action_id: "hello" . into( ) ,
269+ inputs: Vec :: new( ) ,
270+ explicit_outputs: vec![ PathBuf :: from( "say-hello" ) ] ,
271+ implicit_outputs: Vec :: new( ) ,
272+ order_only_deps: Vec :: new( ) ,
273+ phony: true ,
274+ always: false ,
275+ } ,
276+ PathBuf :: from( "action-called.txt" ) ,
277+ vec![ "say-hello" ] ,
278+ AssertionType :: FileExists ,
279+ ) ]
280+ fn ninja_integration_tests (
281+ ninja_integration_setup : Option < TempDir > ,
282+ #[ case] action : Action ,
283+ #[ case] edge : BuildEdge ,
284+ #[ case] target_name : PathBuf ,
285+ #[ case] ninja_args : Vec < & str > ,
286+ #[ case] assertion : AssertionType ,
287+ ) {
288+ let Some ( dir) = ninja_integration_setup else {
289+ return ;
289290 } ;
291+
292+ let output = edge
293+ . explicit_outputs
294+ . first ( )
295+ . expect ( "explicit output" )
296+ . clone ( ) ;
290297 let mut graph = BuildGraph :: default ( ) ;
291- graph. actions . insert ( "tick" . into ( ) , action) ;
292- graph. targets . insert ( PathBuf :: from ( "out" ) , edge) ;
293- graph. default_targets . push ( PathBuf :: from ( "out" ) ) ;
298+ graph. actions . insert ( edge . action_id . clone ( ) , action) ;
299+ graph. targets . insert ( output . clone ( ) , edge) ;
300+ graph. default_targets . push ( output ) ;
294301
295302 let ninja = generate ( & graph) ;
296- let dir = tempdir ( ) . expect ( "temp dir" ) ;
297303 fs:: write ( dir. path ( ) . join ( "build.ninja" ) , & ninja) . expect ( "write ninja" ) ;
298304 let status = Command :: new ( "ninja" )
299- . arg ( "out" )
305+ . args ( & ninja_args )
300306 . current_dir ( dir. path ( ) )
301307 . status ( )
302308 . expect ( "run ninja" ) ;
303- assert ! ( status. success( ) ) ;
304- let content = fs:: read_to_string ( dir. path ( ) . join ( "out" ) ) . expect ( "read out" ) ;
305- assert_eq ! ( content. trim( ) , "hi" ) ;
309+
310+ match assertion {
311+ AssertionType :: StatusSuccess => assert ! ( status. success( ) ) ,
312+ AssertionType :: FileExists => {
313+ assert ! ( status. success( ) ) ;
314+ assert ! ( dir. path( ) . join( target_name) . exists( ) ) ;
315+ }
316+ AssertionType :: FileContent ( expected) => {
317+ assert ! ( status. success( ) ) ;
318+ let content =
319+ fs:: read_to_string ( dir. path ( ) . join ( target_name) ) . expect ( "read target file" ) ;
320+ assert_eq ! ( content. trim( ) , expected) ;
321+ }
322+ }
306323}
0 commit comments