sagiegurari / cargo-make
1
use super::*;
2
use crate::types::{
3
    ConfigSection, CrateInfo, PlatformOverrideTask, Task, TaskWatchOptions, Workspace,
4
};
5
use indexmap::IndexMap;
6
use std::env;
7

8
#[test]
9 8
fn get_task_name_not_found() {
10 8
    let config = Config {
11 8
        config: ConfigSection::new(),
12 8
        env_files: vec![],
13 8
        env: IndexMap::new(),
14 8
        env_scripts: vec![],
15 8
        tasks: IndexMap::new(),
16 0
    };
17

18 8
    let name = get_task_name(&config, "test");
19

20 8
    assert!(name.is_none());
21 8
}
22

23
#[test]
24 8
fn get_task_name_no_alias() {
25 8
    let mut config = Config {
26 8
        config: ConfigSection::new(),
27 8
        env_files: vec![],
28 8
        env: IndexMap::new(),
29 8
        env_scripts: vec![],
30 8
        tasks: IndexMap::new(),
31 0
    };
32

33 8
    config.tasks.insert("test".to_string(), Task::new());
34

35 8
    let name = get_task_name(&config, "test");
36

37 8
    assert_eq!(name.unwrap(), "test");
38 8
}
39

40
#[test]
41 8
fn get_task_name_alias() {
42 8
    let mut config = Config {
43 8
        config: ConfigSection::new(),
44 8
        env_files: vec![],
45 8
        env: IndexMap::new(),
46 8
        env_scripts: vec![],
47 8
        tasks: IndexMap::new(),
48 0
    };
49

50 8
    let mut task = Task::new();
51 8
    task.alias = Some("test2".to_string());
52 8
    config.tasks.insert("test".to_string(), task);
53

54 8
    config.tasks.insert("test2".to_string(), Task::new());
55

56 8
    let name = get_task_name(&config, "test");
57

58 8
    assert_eq!(name.unwrap(), "test2");
59 8
}
60

61
#[test]
62
#[should_panic]
63 8
fn get_task_name_alias_self_referential() {
64 8
    let mut config = Config {
65 8
        config: ConfigSection::new(),
66 8
        env_files: vec![],
67 8
        env: IndexMap::new(),
68 8
        env_scripts: vec![],
69 8
        tasks: IndexMap::new(),
70 0
    };
71

72 8
    let mut task = Task::new();
73 8
    task.alias = Some("rec".to_string());
74 8
    config.tasks.insert("rec".to_string(), task);
75

76 8
    get_task_name(&config, "rec");
77 6
}
78

79
#[test]
80
#[should_panic]
81 8
fn get_task_name_alias_circular() {
82 8
    let mut config = Config {
83 8
        config: ConfigSection::new(),
84 8
        env_files: vec![],
85 8
        env: IndexMap::new(),
86 8
        env_scripts: vec![],
87 8
        tasks: IndexMap::new(),
88 0
    };
89

90 8
    let mut task_a = Task::new();
91 8
    let mut task_b = Task::new();
92

93 8
    task_a.alias = Some("rec-mut-b".to_string());
94 8
    task_b.alias = Some("rec-mut-a".to_string());
95

96 8
    config.tasks.insert("rec-mut-a".to_string(), task_a);
97 8
    config.tasks.insert("rec-mut-b".to_string(), task_b);
98

99 8
    get_task_name(&config, "rec-mut-a");
100 6
}
101

102
#[test]
103 8
fn get_task_name_platform_alias() {
104 8
    let mut config = Config {
105 8
        config: ConfigSection::new(),
106 8
        env_files: vec![],
107 8
        env: IndexMap::new(),
108 8
        env_scripts: vec![],
109 8
        tasks: IndexMap::new(),
110 0
    };
111

112 8
    let mut task = Task::new();
113 8
    if cfg!(windows) {
114 0
        task.windows_alias = Some("test2".to_string());
115 2
    } else if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
116 0
        task.mac_alias = Some("test2".to_string());
117
    } else {
118 8
        task.linux_alias = Some("test2".to_string());
119
    };
120

121 8
    config.tasks.insert("test".to_string(), task);
122

123 8
    config.tasks.insert("test2".to_string(), Task::new());
124

125 8
    let name = get_task_name(&config, "test");
126

127 8
    assert_eq!(name.unwrap(), "test2");
128 8
}
129

130
#[test]
131 8
fn get_workspace_members_config_not_defined_or_empty() {
132 8
    let members = get_workspace_members_config("".to_string());
133

134 8
    assert_eq!(members.len(), 0);
135 8
}
136

137
#[test]
138 8
fn get_workspace_members_config_single() {
139 8
    let members = get_workspace_members_config("test".to_string());
140

141 8
    assert_eq!(members.len(), 1);
142 8
    assert!(members.contains(&"test".to_string()));
143 8
}
144

145
#[test]
146 8
fn get_workspace_members_config_multiple() {
147 8
    let members = get_workspace_members_config("test1;test2;test3".to_string());
148

149 8
    assert_eq!(members.len(), 3);
150 8
    assert!(members.contains(&"test1".to_string()));
151 8
    assert!(members.contains(&"test2".to_string()));
152 8
    assert!(members.contains(&"test3".to_string()));
153 8
}
154

155 8
fn update_member_path_get_expected() -> String {
156 2
    if cfg!(windows) {
157 0
        ".\\member\\".to_string()
158
    } else {
159 8
        "./member/".to_string()
160
    }
161 8
}
162

163
#[test]
164 8
fn update_member_path_unix() {
165 8
    let output = update_member_path("./member/");
166 8
    assert_eq!(output, update_member_path_get_expected());
167 8
}
168

169
#[test]
170 8
fn update_member_path_windows() {
171 8
    let output = update_member_path(".\\member\\");
172 8
    assert_eq!(output, update_member_path_get_expected());
173 8
}
174

175
#[test]
176 8
fn create_workspace_task_no_members() {
177 8
    let mut crate_info = CrateInfo::new();
178 8
    let members = vec![];
179 8
    crate_info.workspace = Some(Workspace {
180 8
        members: Some(members),
181 8
        exclude: None,
182
    });
183

184 8
    let task = create_workspace_task(crate_info, "some_task");
185

186 8
    assert!(task.script.is_some());
187 8
    let script = match task.script.unwrap() {
188 8
        ScriptValue::Text(value) => value.join("\n"),
189 0
        _ => panic!("Invalid script value type."),
190 8
    };
191 8
    assert_eq!(script, "".to_string());
192 8
    assert!(task.env.is_none());
193 8
}
194

195
#[test]
196
#[ignore]
197
#[cfg(target_os = "linux")]
198 2
fn create_workspace_task_with_members() {
199 2
    let mut crate_info = CrateInfo::new();
200 2
    let members = vec![
201 2
        "member1".to_string(),
202 2
        "member2".to_string(),
203 2
        "dir1/member3".to_string(),
204
    ];
205 2
    crate_info.workspace = Some(Workspace {
206 2
        members: Some(members),
207 2
        exclude: None,
208
    });
209

210 2
    envmnt::remove("CARGO_MAKE_USE_WORKSPACE_PROFILE");
211

212 2
    let task = create_workspace_task(crate_info, "some_task");
213

214 2
    let mut expected_script = r#"cd ./member1
215
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member1 --profile PROFILE_NAME -- some_task
216
cd -
217
cd ./member2
218
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member2 --profile PROFILE_NAME -- some_task
219
cd -
220
cd ./dir1/member3
221
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member3 --profile PROFILE_NAME -- some_task
222
cd -"#
223
        .to_string();
224

225 2
    let log_level = logger::get_log_level();
226 2
    expected_script = str::replace(&expected_script, "LEVEL_NAME", &log_level);
227

228 2
    let profile_name = profile::get();
229 2
    expected_script = str::replace(&expected_script, "PROFILE_NAME", &profile_name);
230

231 2
    assert!(task.script.is_some());
232 2
    let script = match task.script.unwrap() {
233 2
        ScriptValue::Text(value) => value.join("\n"),
234 0
        _ => panic!("Invalid script value type."),
235 2
    };
236 2
    assert_eq!(script, expected_script);
237 2
    assert!(task.env.is_none());
238 2
}
239

240
#[test]
241
#[ignore]
242
#[cfg(target_os = "linux")]
243 2
fn create_workspace_task_with_members_no_workspace_profile() {
244 2
    let mut crate_info = CrateInfo::new();
245 2
    let members = vec![
246 2
        "member1".to_string(),
247 2
        "member2".to_string(),
248 2
        "dir1/member3".to_string(),
249
    ];
250 2
    crate_info.workspace = Some(Workspace {
251 2
        members: Some(members),
252 2
        exclude: None,
253
    });
254

255 2
    envmnt::set_bool("CARGO_MAKE_USE_WORKSPACE_PROFILE", false);
256

257 2
    let task = create_workspace_task(crate_info, "some_task");
258

259 2
    let mut expected_script = r#"cd ./member1
260
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member1 --profile development -- some_task
261
cd -
262
cd ./member2
263
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member2 --profile development -- some_task
264
cd -
265
cd ./dir1/member3
266
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member3 --profile development -- some_task
267
cd -"#
268
        .to_string();
269

270 2
    let log_level = logger::get_log_level();
271 2
    expected_script = str::replace(&expected_script, "LEVEL_NAME", &log_level);
272

273 2
    assert!(task.script.is_some());
274 2
    let script = match task.script.unwrap() {
275 2
        ScriptValue::Text(value) => value.join("\n"),
276 0
        _ => panic!("Invalid script value type."),
277 2
    };
278 2
    assert_eq!(script, expected_script);
279 2
    assert!(task.env.is_none());
280 2
}
281

282
#[test]
283
#[ignore]
284
#[cfg(target_os = "linux")]
285 2
fn create_workspace_task_with_members_and_arguments() {
286 2
    let mut crate_info = CrateInfo::new();
287 2
    let members = vec![
288 2
        "member1".to_string(),
289 2
        "member2".to_string(),
290 2
        "dir1/member3".to_string(),
291
    ];
292 2
    crate_info.workspace = Some(Workspace {
293 2
        members: Some(members),
294 2
        exclude: None,
295
    });
296

297 2
    envmnt::remove("CARGO_MAKE_USE_WORKSPACE_PROFILE");
298

299 2
    envmnt::set_list(
300
        "CARGO_MAKE_TASK_ARGS",
301 2
        &vec!["arg1".to_string(), "arg2".to_string()],
302 2
    );
303

304 2
    let task = create_workspace_task(crate_info, "some_task");
305

306 2
    envmnt::remove("CARGO_MAKE_TASK_ARGS");
307

308 2
    let mut expected_script = r#"cd ./member1
309
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member1 --profile PROFILE_NAME -- some_task arg1 arg2
310
cd -
311
cd ./member2
312
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member2 --profile PROFILE_NAME -- some_task arg1 arg2
313
cd -
314
cd ./dir1/member3
315
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member3 --profile PROFILE_NAME -- some_task arg1 arg2
316
cd -"#
317
        .to_string();
318

319 2
    let log_level = logger::get_log_level();
320 2
    expected_script = str::replace(&expected_script, "LEVEL_NAME", &log_level);
321

322 2
    let profile_name = profile::get();
323 2
    expected_script = str::replace(&expected_script, "PROFILE_NAME", &profile_name);
324

325 2
    assert!(task.script.is_some());
326 2
    let script = match task.script.unwrap() {
327 2
        ScriptValue::Text(value) => value.join("\n"),
328 0
        _ => panic!("Invalid script value type."),
329 2
    };
330 2
    assert_eq!(script, expected_script);
331 2
    assert!(task.env.is_none());
332 2
}
333

334
#[test]
335
#[ignore]
336 2
fn create_workspace_task_with_included_members() {
337 2
    let mut crate_info = CrateInfo::new();
338 2
    let members = vec![
339 2
        "member1".to_string(),
340 2
        "member2".to_string(),
341 2
        "dir1/member3".to_string(),
342 2
        "dir1/member4".to_string(),
343
    ];
344 2
    crate_info.workspace = Some(Workspace {
345 2
        members: Some(members),
346 2
        exclude: None,
347
    });
348

349 2
    envmnt::set_list(
350
        "CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS",
351 2
        &vec![
352 2
            "member1".to_string(),
353 2
            "member2".to_string(),
354 2
            "dir1/member3".to_string(),
355
        ],
356 2
    );
357

358 2
    profile::set(profile::DEFAULT_PROFILE);
359

360 2
    let task = create_workspace_task(crate_info, "some_task");
361

362 2
    envmnt::remove("CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS");
363

364 0
    let mut expected_script = if cfg!(windows) {
365 0
        r#"PUSHD member1
366
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member1 --profile development -- some_task
367
if %errorlevel% neq 0 exit /b %errorlevel%
368
POPD
369
PUSHD member2
370
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member2 --profile development -- some_task
371
if %errorlevel% neq 0 exit /b %errorlevel%
372
POPD
373
PUSHD dir1\member3
374
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member3 --profile development -- some_task
375
if %errorlevel% neq 0 exit /b %errorlevel%
376
POPD"#.to_string()
377
    } else {
378 2
        r#"cd ./member1
379
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member1 --profile development -- some_task
380
cd -
381
cd ./member2
382
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member2 --profile development -- some_task
383
cd -
384
cd ./dir1/member3
385
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member3 --profile development -- some_task
386
cd -"#.to_string()
387
    };
388

389 2
    let log_level = logger::get_log_level();
390 2
    expected_script = str::replace(&expected_script, "LEVEL_NAME", &log_level);
391

392 2
    assert!(task.script.is_some());
393 2
    let script = match task.script.unwrap() {
394 2
        ScriptValue::Text(value) => value.join("\n"),
395 0
        _ => panic!("Invalid script value type."),
396 2
    };
397 2
    assert_eq!(script, expected_script);
398 2
    assert!(task.env.is_none());
399 2
}
400

401
#[test]
402
#[ignore]
403
#[cfg(target_os = "linux")]
404 2
fn create_workspace_task_with_included_and_skipped_members() {
405 2
    let mut crate_info = CrateInfo::new();
406 2
    let members = vec![
407 2
        "member1".to_string(),
408 2
        "member2".to_string(),
409 2
        "dir1/member3".to_string(),
410
    ];
411 2
    crate_info.workspace = Some(Workspace {
412 2
        members: Some(members),
413 2
        exclude: None,
414
    });
415

416 2
    envmnt::set_list(
417
        "CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS",
418 2
        &vec!["member1".to_string(), "member2".to_string()],
419 2
    );
420

421 2
    envmnt::set_list(
422
        "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS",
423 2
        &vec!["member2".to_string(), "dir1/member3".to_string()],
424 2
    );
425

426 2
    profile::set(profile::DEFAULT_PROFILE);
427

428 2
    let task = create_workspace_task(crate_info, "some_task");
429

430 2
    envmnt::remove("CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS");
431 2
    envmnt::remove("CARGO_MAKE_WORKSPACE_SKIP_MEMBERS");
432

433 2
    let mut expected_script = r#"cd ./member1
434
cargo make --disable-check-for-updates --allow-private --no-on-error --loglevel=LEVEL_NAME --env CARGO_MAKE_CRATE_CURRENT_WORKSPACE_MEMBER=member1 --profile development -- some_task
435
cd -"#
436
        .to_string();
437

438 2
    let log_level = logger::get_log_level();
439 2
    expected_script = str::replace(&expected_script, "LEVEL_NAME", &log_level);
440

441 2
    assert!(task.script.is_some());
442 2
    let script = match task.script.unwrap() {
443 2
        ScriptValue::Text(value) => value.join("\n"),
444 0
        _ => panic!("Invalid script value type."),
445 2
    };
446 2
    assert_eq!(script, expected_script);
447 2
    assert!(task.env.is_none());
448 2
}
449

450
#[test]
451
#[ignore]
452 2
fn create_workspace_task_extend_workspace_makefile() {
453 2
    let mut crate_info = CrateInfo::new();
454 2
    let members = vec![];
455 2
    crate_info.workspace = Some(Workspace {
456 2
        members: Some(members),
457 2
        exclude: None,
458
    });
459

460 2
    envmnt::set("CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE", "true");
461 2
    let task = create_workspace_task(crate_info, "some_task");
462 2
    envmnt::set("CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE", "false");
463

464 2
    assert!(task.script.is_some());
465 2
    let script = match task.script.unwrap() {
466 2
        ScriptValue::Text(value) => value.join("\n"),
467 0
        _ => panic!("Invalid script value type."),
468 2
    };
469 2
    assert_eq!(script, "".to_string());
470 2
    assert!(task.env.is_some());
471 2
    assert!(task
472
        .env
473
        .unwrap()
474
        .get("CARGO_MAKE_WORKSPACE_MAKEFILE")
475
        .is_some());
476 2
}
477

478
#[test]
479 8
fn is_workspace_flow_true_default() {
480 8
    let mut crate_info = CrateInfo::new();
481 8
    let members = vec![];
482 8
    crate_info.workspace = Some(Workspace {
483 8
        members: Some(members),
484 8
        exclude: None,
485
    });
486

487 8
    let task = Task::new();
488

489 8
    let mut config = Config {
490 8
        config: ConfigSection::new(),
491 8
        env_files: vec![],
492 8
        env: IndexMap::new(),
493 8
        env_scripts: vec![],
494 8
        tasks: IndexMap::new(),
495 0
    };
496 8
    config.tasks.insert("test".to_string(), task);
497

498 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, false);
499

500 8
    assert!(workspace_flow);
501 8
}
502

503
#[test]
504 8
fn is_workspace_flow_false_in_config() {
505 8
    let mut crate_info = CrateInfo::new();
506 8
    let members = vec![];
507 8
    crate_info.workspace = Some(Workspace {
508 8
        members: Some(members),
509 8
        exclude: None,
510
    });
511

512 8
    let task = Task::new();
513

514 8
    let mut config_section = ConfigSection::new();
515 8
    config_section.default_to_workspace = Some(false);
516

517 8
    let mut config = Config {
518 8
        config: config_section,
519 8
        env_files: vec![],
520 8
        env: IndexMap::new(),
521 8
        env_scripts: vec![],
522 8
        tasks: IndexMap::new(),
523 0
    };
524 8
    config.tasks.insert("test".to_string(), task);
525

526 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, false);
527

528 8
    assert!(!workspace_flow);
529 8
}
530

531
#[test]
532 8
fn is_workspace_flow_true_in_config() {
533 8
    let mut crate_info = CrateInfo::new();
534 8
    let members = vec![];
535 8
    crate_info.workspace = Some(Workspace {
536 8
        members: Some(members),
537 8
        exclude: None,
538
    });
539

540 8
    let task = Task::new();
541

542 8
    let mut config_section = ConfigSection::new();
543 8
    config_section.default_to_workspace = Some(true);
544

545 8
    let mut config = Config {
546 8
        config: config_section,
547 8
        env_files: vec![],
548 8
        env: IndexMap::new(),
549 8
        env_scripts: vec![],
550 8
        tasks: IndexMap::new(),
551 0
    };
552 8
    config.tasks.insert("test".to_string(), task);
553

554 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, false);
555

556 8
    assert!(workspace_flow);
557 8
}
558

559
#[test]
560 8
fn is_workspace_flow_true_in_task() {
561 8
    let mut crate_info = CrateInfo::new();
562 8
    let members = vec![];
563 8
    crate_info.workspace = Some(Workspace {
564 8
        members: Some(members),
565 8
        exclude: None,
566
    });
567

568 8
    let mut task = Task::new();
569 8
    task.workspace = Some(true);
570

571 8
    let mut config = Config {
572 8
        config: ConfigSection::new(),
573 8
        env_files: vec![],
574 8
        env: IndexMap::new(),
575 8
        env_scripts: vec![],
576 8
        tasks: IndexMap::new(),
577 0
    };
578 8
    config.tasks.insert("test".to_string(), task);
579

580 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, false);
581

582 8
    assert!(workspace_flow);
583 8
}
584

585
#[test]
586 8
fn is_workspace_flow_default_false_in_task_and_sub_flow() {
587 8
    let mut crate_info = CrateInfo::new();
588 8
    let members = vec![];
589 8
    crate_info.workspace = Some(Workspace {
590 8
        members: Some(members),
591 8
        exclude: None,
592
    });
593

594 8
    let task = Task::new();
595

596 8
    let mut config = Config {
597 8
        config: ConfigSection::new(),
598 8
        env_files: vec![],
599 8
        env: IndexMap::new(),
600 8
        env_scripts: vec![],
601 8
        tasks: IndexMap::new(),
602 0
    };
603 8
    config.tasks.insert("test".to_string(), task);
604

605 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, true);
606

607 8
    assert!(!workspace_flow);
608 8
}
609

610
#[test]
611 8
fn is_workspace_flow_true_in_task_and_sub_flow() {
612 8
    let mut crate_info = CrateInfo::new();
613 8
    let members = vec![];
614 8
    crate_info.workspace = Some(Workspace {
615 8
        members: Some(members),
616 8
        exclude: None,
617
    });
618

619 8
    let mut task = Task::new();
620 8
    task.workspace = Some(true);
621

622 8
    let mut config = Config {
623 8
        config: ConfigSection::new(),
624 8
        env_files: vec![],
625 8
        env: IndexMap::new(),
626 8
        env_scripts: vec![],
627 8
        tasks: IndexMap::new(),
628 0
    };
629 8
    config.tasks.insert("test".to_string(), task);
630

631 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, true);
632

633 8
    assert!(workspace_flow);
634 8
}
635

636
#[test]
637 8
fn is_workspace_flow_false_in_task_and_sub_flow() {
638 8
    let mut crate_info = CrateInfo::new();
639 8
    let members = vec![];
640 8
    crate_info.workspace = Some(Workspace {
641 8
        members: Some(members),
642 8
        exclude: None,
643
    });
644

645 8
    let mut task = Task::new();
646 8
    task.workspace = Some(false);
647

648 8
    let mut config = Config {
649 8
        config: ConfigSection::new(),
650 8
        env_files: vec![],
651 8
        env: IndexMap::new(),
652 8
        env_scripts: vec![],
653 8
        tasks: IndexMap::new(),
654 0
    };
655 8
    config.tasks.insert("test".to_string(), task);
656

657 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, true);
658

659 8
    assert!(!workspace_flow);
660 8
}
661

662
#[test]
663 8
fn is_workspace_flow_task_not_defined() {
664 8
    let mut crate_info = CrateInfo::new();
665 8
    let members = vec![];
666 8
    crate_info.workspace = Some(Workspace {
667 8
        members: Some(members),
668 8
        exclude: None,
669
    });
670

671 8
    let config = Config {
672 8
        config: ConfigSection::new(),
673 8
        env_files: vec![],
674 8
        env: IndexMap::new(),
675 8
        env_scripts: vec![],
676 8
        tasks: IndexMap::new(),
677 0
    };
678

679 8
    let workspace_flow = is_workspace_flow(&config, "notfound", false, &crate_info, false);
680

681 8
    assert!(workspace_flow);
682 8
}
683

684
#[test]
685 8
fn is_workspace_flow_no_workspace() {
686 8
    let crate_info = CrateInfo::new();
687

688 8
    let mut task = Task::new();
689 8
    task.workspace = Some(true);
690

691 8
    let mut config = Config {
692 8
        config: ConfigSection::new(),
693 8
        env_files: vec![],
694 8
        env: IndexMap::new(),
695 8
        env_scripts: vec![],
696 8
        tasks: IndexMap::new(),
697 0
    };
698 8
    config.tasks.insert("test".to_string(), task);
699

700 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, false);
701

702 8
    assert!(!workspace_flow);
703 8
}
704

705
#[test]
706 8
fn is_workspace_flow_disabled_via_cli() {
707 8
    let mut crate_info = CrateInfo::new();
708 8
    let members = vec![];
709 8
    crate_info.workspace = Some(Workspace {
710 8
        members: Some(members),
711 8
        exclude: None,
712
    });
713

714 8
    let mut task = Task::new();
715 8
    task.workspace = Some(true);
716

717 8
    let mut config = Config {
718 8
        config: ConfigSection::new(),
719 8
        env_files: vec![],
720 8
        env: IndexMap::new(),
721 8
        env_scripts: vec![],
722 8
        tasks: IndexMap::new(),
723 0
    };
724 8
    config.tasks.insert("test".to_string(), task);
725

726 8
    let workspace_flow = is_workspace_flow(&config, "test", true, &crate_info, false);
727

728 8
    assert!(!workspace_flow);
729 8
}
730

731
#[test]
732 8
fn is_workspace_flow_disabled_via_task() {
733 8
    let mut crate_info = CrateInfo::new();
734 8
    let members = vec![];
735 8
    crate_info.workspace = Some(Workspace {
736 8
        members: Some(members),
737 8
        exclude: None,
738
    });
739

740 8
    let mut task = Task::new();
741 8
    task.workspace = Some(false);
742

743 8
    let mut config = Config {
744 8
        config: ConfigSection::new(),
745 8
        env_files: vec![],
746 8
        env: IndexMap::new(),
747 8
        env_scripts: vec![],
748 8
        tasks: IndexMap::new(),
749 0
    };
750 8
    config.tasks.insert("test".to_string(), task);
751

752 8
    let workspace_flow = is_workspace_flow(&config, "test", false, &crate_info, false);
753

754 8
    assert!(!workspace_flow);
755 8
}
756

757
#[test]
758 8
fn create_single() {
759 8
    let mut config_section = ConfigSection::new();
760 8
    config_section.init_task = Some("init".to_string());
761 8
    config_section.end_task = Some("end".to_string());
762 8
    let mut config = Config {
763 8
        config: config_section,
764 8
        env_files: vec![],
765 8
        env: IndexMap::new(),
766 8
        env_scripts: vec![],
767 8
        tasks: IndexMap::new(),
768 0
    };
769

770 8
    config.tasks.insert("init".to_string(), Task::new());
771 8
    config.tasks.insert("end".to_string(), Task::new());
772

773 8
    let task = Task::new();
774

775 8
    config.tasks.insert("test".to_string(), task);
776

777 8
    let execution_plan = create(&config, "test", false, true, false);
778 8
    assert_eq!(execution_plan.steps.len(), 3);
779 8
    assert_eq!(execution_plan.steps[0].name, "init");
780 8
    assert_eq!(execution_plan.steps[1].name, "test");
781 8
    assert_eq!(execution_plan.steps[2].name, "end");
782 8
}
783

784
#[test]
785 8
fn create_single_disabled() {
786 8
    let mut config_section = ConfigSection::new();
787 8
    config_section.init_task = Some("init".to_string());
788 8
    config_section.end_task = Some("end".to_string());
789 8
    let mut config = Config {
790 8
        config: config_section,
791 8
        env_files: vec![],
792 8
        env: IndexMap::new(),
793 8
        env_scripts: vec![],
794 8
        tasks: IndexMap::new(),
795 0
    };
796

797 8
    config.tasks.insert("init".to_string(), Task::new());
798 8
    config.tasks.insert("end".to_string(), Task::new());
799

800 8
    let mut task = Task::new();
801 8
    task.disabled = Some(true);
802

803 8
    config.tasks.insert("test".to_string(), task);
804

805 8
    let execution_plan = create(&config, "test", false, true, false);
806 8
    assert_eq!(execution_plan.steps.len(), 2);
807 8
    assert_eq!(execution_plan.steps[0].name, "init");
808 8
    assert_eq!(execution_plan.steps[1].name, "end");
809 8
}
810

811
#[test]
812
#[should_panic]
813 8
fn create_single_private() {
814 8
    let mut config_section = ConfigSection::new();
815 8
    config_section.init_task = Some("init".to_string());
816 8
    config_section.end_task = Some("end".to_string());
817 8
    let mut config = Config {
818 8
        config: config_section,
819 8
        env_files: vec![],
820 8
        env: IndexMap::new(),
821 8
        env_scripts: vec![],
822 8
        tasks: IndexMap::new(),
823 0
    };
824

825 8
    config.tasks.insert("init".to_string(), Task::new());
826 8
    config.tasks.insert("end".to_string(), Task::new());
827

828 8
    let mut task = Task::new();
829 8
    task.private = Some(true);
830

831 8
    config.tasks.insert("test-private".to_string(), task);
832

833 8
    create(&config, "test-private", false, false, false);
834 6
}
835

836
#[test]
837 8
fn create_single_allow_private() {
838 8
    let mut config_section = ConfigSection::new();
839 8
    config_section.init_task = Some("init".to_string());
840 8
    config_section.end_task = Some("end".to_string());
841 8
    let mut config = Config {
842 8
        config: config_section,
843 8
        env_files: vec![],
844 8
        env: IndexMap::new(),
845 8
        env_scripts: vec![],
846 8
        tasks: IndexMap::new(),
847 0
    };
848

849 8
    config.tasks.insert("init".to_string(), Task::new());
850 8
    config.tasks.insert("end".to_string(), Task::new());
851

852 8
    let mut task = Task::new();
853 8
    task.private = Some(true);
854

855 8
    config.tasks.insert("test-private".to_string(), task);
856

857 8
    let execution_plan = create(&config, "test-private", false, true, false);
858 8
    assert_eq!(execution_plan.steps.len(), 3);
859 8
    assert_eq!(execution_plan.steps[0].name, "init");
860 8
    assert_eq!(execution_plan.steps[1].name, "test-private");
861 8
    assert_eq!(execution_plan.steps[2].name, "end");
862 8
}
863

864
#[test]
865 8
fn create_with_dependencies() {
866 8
    let mut config_section = ConfigSection::new();
867 8
    config_section.init_task = Some("init".to_string());
868 8
    config_section.end_task = Some("end".to_string());
869 8
    let mut config = Config {
870 8
        config: config_section,
871 8
        env_files: vec![],
872 8
        env: IndexMap::new(),
873 8
        env_scripts: vec![],
874 8
        tasks: IndexMap::new(),
875 0
    };
876

877 8
    config.tasks.insert("init".to_string(), Task::new());
878 8
    config.tasks.insert("end".to_string(), Task::new());
879

880 8
    let mut task = Task::new();
881 8
    task.dependencies = Some(vec!["task_dependency".to_string()]);
882

883 8
    let task_dependency = Task::new();
884

885 8
    config.tasks.insert("test".to_string(), task);
886 8
    config
887
        .tasks
888 8
        .insert("task_dependency".to_string(), task_dependency);
889

890 8
    let execution_plan = create(&config, "test", false, true, false);
891 8
    assert_eq!(execution_plan.steps.len(), 4);
892 8
    assert_eq!(execution_plan.steps[0].name, "init");
893 8
    assert_eq!(execution_plan.steps[1].name, "task_dependency");
894 8
    assert_eq!(execution_plan.steps[2].name, "test");
895 8
    assert_eq!(execution_plan.steps[3].name, "end");
896 8
}
897

898
#[test]
899 8
fn create_with_dependencies_sub_flow() {
900 8
    let mut config_section = ConfigSection::new();
901 8
    config_section.init_task = Some("init".to_string());
902 8
    config_section.end_task = Some("end".to_string());
903 8
    let mut config = Config {
904 8
        config: config_section,
905 8
        env_files: vec![],
906 8
        env: IndexMap::new(),
907 8
        env_scripts: vec![],
908 8
        tasks: IndexMap::new(),
909 0
    };
910

911 8
    config.tasks.insert("init".to_string(), Task::new());
912 8
    config.tasks.insert("end".to_string(), Task::new());
913

914 8
    let mut task = Task::new();
915 8
    task.dependencies = Some(vec!["task_dependency".to_string()]);
916

917 8
    let task_dependency = Task::new();
918

919 8
    config.tasks.insert("test".to_string(), task);
920 8
    config
921
        .tasks
922 8
        .insert("task_dependency".to_string(), task_dependency);
923

924 8
    let execution_plan = create(&config, "test", false, true, true);
925 8
    assert_eq!(execution_plan.steps.len(), 2);
926 8
    assert_eq!(execution_plan.steps[0].name, "task_dependency");
927 8
    assert_eq!(execution_plan.steps[1].name, "test");
928 8
}
929

930
#[test]
931 8
fn create_disabled_task_with_dependencies() {
932 8
    let mut config_section = ConfigSection::new();
933 8
    config_section.init_task = Some("init".to_string());
934 8
    config_section.end_task = Some("end".to_string());
935 8
    let mut config = Config {
936 8
        config: config_section,
937 8
        env_files: vec![],
938 8
        env: IndexMap::new(),
939 8
        env_scripts: vec![],
940 8
        tasks: IndexMap::new(),
941 0
    };
942

943 8
    config.tasks.insert("init".to_string(), Task::new());
944 8
    config.tasks.insert("end".to_string(), Task::new());
945

946 8
    let mut task = Task::new();
947 8
    task.disabled = Some(true);
948 8
    task.dependencies = Some(vec!["task_dependency".to_string()]);
949

950 8
    let task_dependency = Task::new();
951

952 8
    config.tasks.insert("test".to_string(), task);
953 8
    config
954
        .tasks
955 8
        .insert("task_dependency".to_string(), task_dependency);
956

957 8
    let execution_plan = create(&config, "test", false, true, false);
958 8
    assert_eq!(execution_plan.steps.len(), 2);
959 8
    assert_eq!(execution_plan.steps[0].name, "init");
960 8
    assert_eq!(execution_plan.steps[1].name, "end");
961 8
}
962

963
#[test]
964 8
fn create_with_dependencies_disabled() {
965 8
    let mut config_section = ConfigSection::new();
966 8
    config_section.init_task = Some("init".to_string());
967 8
    config_section.end_task = Some("end".to_string());
968 8
    let mut config = Config {
969 8
        config: config_section,
970 8
        env_files: vec![],
971 8
        env: IndexMap::new(),
972 8
        env_scripts: vec![],
973 8
        tasks: IndexMap::new(),
974 0
    };
975

976 8
    config.tasks.insert("init".to_string(), Task::new());
977 8
    config.tasks.insert("end".to_string(), Task::new());
978

979 8
    let mut task = Task::new();
980 8
    task.dependencies = Some(vec!["task_dependency".to_string()]);
981

982 8
    let mut task_dependency = Task::new();
983 8
    task_dependency.disabled = Some(true);
984

985 8
    config.tasks.insert("test".to_string(), task);
986 8
    config
987
        .tasks
988 8
        .insert("task_dependency".to_string(), task_dependency);
989

990 8
    let execution_plan = create(&config, "test", false, true, false);
991 8
    assert_eq!(execution_plan.steps.len(), 3);
992 8
    assert_eq!(execution_plan.steps[0].name, "init");
993 8
    assert_eq!(execution_plan.steps[1].name, "test");
994 8
    assert_eq!(execution_plan.steps[2].name, "end");
995 8
}
996

997
#[test]
998 8
fn create_platform_disabled() {
999 8
    let mut config = Config {
1000 8
        config: ConfigSection::new(),
1001 8
        env_files: vec![],
1002 8
        env: IndexMap::new(),
1003 8
        env_scripts: vec![],
1004 8
        tasks: IndexMap::new(),
1005 0
    };
1006

1007 8
    let mut task = Task::new();
1008 8
    task.linux = Some(PlatformOverrideTask {
1009 8
        clear: Some(true),
1010 8
        disabled: Some(true),
1011 8
        private: Some(false),
1012 8
        deprecated: None,
1013 8
        extend: None,
1014 8
        watch: Some(TaskWatchOptions::Boolean(false)),
1015 8
        condition: None,
1016 8
        condition_script: None,
1017 8
        install_crate: None,
1018 8
        install_crate_args: None,
1019 8
        command: None,
1020 8
        ignore_errors: None,
1021 8
        force: None,
1022 8
        env_files: None,
1023 8
        env: None,
1024 8
        cwd: None,
1025 8
        install_script: None,
1026 8
        args: None,
1027 8
        script: None,
1028 8
        script_runner: None,
1029 8
        script_runner_args: None,
1030 8
        script_extension: None,
1031 8
        run_task: None,
1032 8
        dependencies: None,
1033 8
        toolchain: None,
1034
    });
1035 8
    task.windows = Some(PlatformOverrideTask {
1036 8
        clear: Some(true),
1037 8
        disabled: Some(true),
1038 8
        private: Some(false),
1039 8
        deprecated: None,
1040 8
        extend: None,
1041 8
        watch: Some(TaskWatchOptions::Boolean(false)),
1042 8
        condition: None,
1043 8
        condition_script: None,
1044 8
        install_crate: None,
1045 8
        install_crate_args: None,
1046 8
        command: None,
1047 8
        ignore_errors: None,
1048 8
        force: None,
1049 8
        env_files: None,
1050 8
        env: None,
1051 8
        cwd: None,
1052 8
        install_script: None,
1053 8
        args: None,
1054 8
        script: None,
1055 8
        script_runner: None,
1056 8
        script_runner_args: None,
1057 8
        script_extension: None,
1058 8
        run_task: None,
1059 8
        dependencies: None,
1060 8
        toolchain: None,
1061
    });
1062 8
    task.mac = Some(PlatformOverrideTask {
1063 8
        clear: Some(true),
1064 8
        disabled: Some(true),
1065 8
        private: Some(false),
1066 8
        deprecated: None,
1067 8
        extend: None,
1068 8
        watch: Some(TaskWatchOptions::Boolean(false)),
1069 8
        condition: None,
1070 8
        condition_script: None,
1071 8
        install_crate: None,
1072 8
        install_crate_args: None,
1073 8
        command: None,
1074 8
        ignore_errors: None,
1075 8
        force: None,
1076 8
        env_files: None,
1077 8
        env: None,
1078 8
        cwd: None,
1079 8
        install_script: None,
1080 8
        args: None,
1081 8
        script: None,
1082 8
        script_runner: None,
1083 8
        script_runner_args: None,
1084 8
        script_extension: None,
1085 8
        run_task: None,
1086 8
        dependencies: None,
1087 8
        toolchain: None,
1088
    });
1089

1090 8
    config.tasks.insert("test".to_string(), task);
1091

1092 8
    let execution_plan = create(&config, "test", false, true, false);
1093 8
    assert_eq!(execution_plan.steps.len(), 0);
1094 8
}
1095

1096
#[test]
1097
#[ignore]
1098 2
fn create_workspace() {
1099 2
    let mut config = Config {
1100 2
        config: ConfigSection::new(),
1101 2
        env_files: vec![],
1102 2
        env: IndexMap::new(),
1103 2
        env_scripts: vec![],
1104 2
        tasks: IndexMap::new(),
1105 0
    };
1106

1107 2
    let task = Task::new();
1108

1109 2
    config.tasks.insert("test".to_string(), task);
1110

1111 2
    env::set_current_dir("./examples/workspace").unwrap();
1112 2
    let execution_plan = create(&config, "test", false, true, false);
1113 2
    env::set_current_dir("../../").unwrap();
1114 2
    assert_eq!(execution_plan.steps.len(), 1);
1115 2
    assert_eq!(execution_plan.steps[0].name, "workspace");
1116 2
}
1117

1118
#[test]
1119
#[ignore]
1120 2
fn create_noworkspace() {
1121 2
    let mut config = Config {
1122 2
        config: ConfigSection::new(),
1123 2
        env_files: vec![],
1124 2
        env: IndexMap::new(),
1125 2
        env_scripts: vec![],
1126 2
        tasks: IndexMap::new(),
1127 0
    };
1128

1129 2
    let task = Task::new();
1130

1131 2
    config.tasks.insert("test".to_string(), task);
1132

1133 2
    env::set_current_dir("./examples/workspace").unwrap();
1134 2
    let execution_plan = create(&config, "test", true, true, false);
1135 2
    env::set_current_dir("../../").unwrap();
1136 2
    assert_eq!(execution_plan.steps.len(), 1);
1137 2
    assert_eq!(execution_plan.steps[0].name, "test");
1138 2
}
1139

1140
#[test]
1141 8
fn should_skip_workspace_member_empty() {
1142 8
    let skipped_members = HashSet::new();
1143

1144 8
    let skip = should_skip_workspace_member("member", &skipped_members);
1145

1146 8
    assert!(!skip);
1147 8
}
1148

1149
#[test]
1150 8
fn should_skip_workspace_member_not_found_string() {
1151 8
    let mut skipped_members = HashSet::new();
1152 8
    skipped_members.insert("test1".to_string());
1153 8
    skipped_members.insert("test2".to_string());
1154 8
    skipped_members.insert("test3".to_string());
1155

1156 8
    let skip = should_skip_workspace_member("member", &skipped_members);
1157

1158 8
    assert!(!skip);
1159 8
}
1160

1161
#[test]
1162 8
fn should_skip_workspace_member_found_string() {
1163 8
    let mut skipped_members = HashSet::new();
1164 8
    skipped_members.insert("test1".to_string());
1165 8
    skipped_members.insert("test2".to_string());
1166 8
    skipped_members.insert("member".to_string());
1167 8
    skipped_members.insert("test3".to_string());
1168

1169 8
    let skip = should_skip_workspace_member("member", &skipped_members);
1170

1171 8
    assert!(skip);
1172 8
}
1173

1174
#[test]
1175 8
fn should_skip_workspace_member_not_found_glob() {
1176 8
    let mut skipped_members = HashSet::new();
1177 8
    skipped_members.insert("test1".to_string());
1178 8
    skipped_members.insert("test2".to_string());
1179 8
    skipped_members.insert("test3".to_string());
1180 8
    skipped_members.insert("test/*".to_string());
1181

1182 8
    let skip = should_skip_workspace_member("test1/member", &skipped_members);
1183

1184 8
    assert!(!skip);
1185 8
}
1186

1187
#[test]
1188 8
fn should_skip_workspace_member_found_glob() {
1189 8
    let mut skipped_members = HashSet::new();
1190 8
    skipped_members.insert("test1".to_string());
1191 8
    skipped_members.insert("test2".to_string());
1192 8
    skipped_members.insert("test3".to_string());
1193 8
    skipped_members.insert("members/*".to_string());
1194

1195 8
    let skip = should_skip_workspace_member("members/test", &skipped_members);
1196

1197 8
    assert!(skip);
1198 8
}
1199

1200
#[test]
1201 8
fn get_normalized_task_multi_extend() {
1202 8
    let mut task1 = Task::new();
1203 8
    task1.category = Some("1".to_string());
1204 8
    task1.description = Some("1".to_string());
1205 8
    task1.command = Some("echo".to_string());
1206 8
    task1.args = Some(vec!["1".to_string()]);
1207

1208 8
    let platform_task = PlatformOverrideTask {
1209 8
        clear: None,
1210 8
        disabled: None,
1211 8
        private: None,
1212 8
        deprecated: None,
1213 8
        extend: None,
1214 8
        watch: None,
1215 8
        condition: None,
1216 8
        condition_script: None,
1217 8
        install_crate: None,
1218 8
        install_crate_args: None,
1219 8
        command: None,
1220 8
        ignore_errors: None,
1221 8
        force: Some(true),
1222 8
        env_files: None,
1223 8
        env: None,
1224 8
        cwd: None,
1225 8
        install_script: None,
1226 8
        args: None,
1227 8
        script: None,
1228 8
        script_runner: None,
1229 8
        script_runner_args: None,
1230 8
        script_extension: None,
1231 8
        run_task: None,
1232 8
        dependencies: None,
1233 8
        toolchain: None,
1234
    };
1235

1236 8
    let mut task2 = Task::new();
1237 8
    task2.extend = Some("1".to_string());
1238 8
    task2.category = Some("2".to_string());
1239 8
    task2.args = Some(vec!["2".to_string()]);
1240 8
    task2.linux = Some(platform_task.clone());
1241 8
    task2.mac = Some(platform_task.clone());
1242 8
    task2.windows = Some(platform_task.clone());
1243

1244 8
    let mut task3 = Task::new();
1245 8
    task3.extend = Some("2".to_string());
1246 8
    task3.args = Some(vec!["3".to_string()]);
1247

1248 8
    let mut config = Config {
1249 8
        config: ConfigSection::new(),
1250 8
        env_files: vec![],
1251 8
        env: IndexMap::new(),
1252 8
        env_scripts: vec![],
1253 8
        tasks: IndexMap::new(),
1254 0
    };
1255 8
    config.tasks.insert("1".to_string(), task1);
1256 8
    config.tasks.insert("2".to_string(), task2);
1257 8
    config.tasks.insert("3".to_string(), task3);
1258

1259 8
    let task = get_normalized_task(&config, "3", true);
1260

1261 8
    assert_eq!(task.category.unwrap(), "2");
1262 8
    assert_eq!(task.description.unwrap(), "1");
1263 8
    assert_eq!(task.command.unwrap(), "echo");
1264 8
    assert_eq!(task.args.unwrap(), vec!["3".to_string()]);
1265 8
    assert_eq!(task.extend.unwrap(), "2");
1266 8
    assert!(task.force.unwrap());
1267 8
}
1268

1269
#[test]
1270 8
fn get_normalized_task_simple() {
1271 8
    let mut task1 = Task::new();
1272 8
    task1.category = Some("1".to_string());
1273 8
    task1.description = Some("1".to_string());
1274 8
    task1.command = Some("echo".to_string());
1275 8
    task1.args = Some(vec!["1".to_string()]);
1276

1277 8
    let mut config = Config {
1278 8
        config: ConfigSection::new(),
1279 8
        env_files: vec![],
1280 8
        env: IndexMap::new(),
1281 8
        env_scripts: vec![],
1282 8
        tasks: IndexMap::new(),
1283 0
    };
1284 8
    config.tasks.insert("1".to_string(), task1);
1285

1286 8
    let task = get_normalized_task(&config, "1", true);
1287

1288 8
    assert_eq!(task.category.unwrap(), "1");
1289 8
    assert_eq!(task.description.unwrap(), "1");
1290 8
    assert_eq!(task.command.unwrap(), "echo");
1291 8
    assert_eq!(task.args.unwrap(), vec!["1".to_string()]);
1292 8
}

Read our documentation on viewing source code .

Loading