Grcov report - task.rs

1

    use anyhow::Context;

2

    use hashbrown::HashMap;

3

    use indicatif::{

4

      HumanDuration,

5

      ProgressBar,

6

      ProgressStyle,

7

    };

8

    use rand::Rng as _;

9

    use serde::Deserialize;

10

11

    use std::io::{

12

      BufRead as _,

13

      BufReader,

14

    };

15

    use std::process::Command as ProcessCommand;

16

    use std::time::{

17

      Duration,

18

      Instant,

19

    };

20

    use std::{

21

      fs,

22

      thread,

23

    };

24

25

    use super::{

26

      is_shell_command,

27

      CommandRunner,

28

      Precondition,

29

      TaskContext,

30

      TaskDependency,

31

    };

32

    use crate::defaults::{

33

      default_shell,

34

      default_verbose,

35

    };

36

    use crate::schema::get_output_handler;

37

    use crate::utils::deserialize_environment;

38

    use crate::{

39

      handle_output,

40

      run_shell_command,

41

    };

42

43

    /// This struct represents a task that can be executed. A task can contain multiple

44

    /// commands that are executed sequentially. A task can also have preconditions that

45

    /// must be met before the task can be executed.

46

    #[derive(Debug, Default, Deserialize)]

47

    pub struct TaskArgs {

48

      /// The commands to run

49

      pub commands: Vec<CommandRunner>,

50

51

      /// The preconditions that must be met before the task can be executed

52

      #[serde(default)]

53

      pub preconditions: Vec<Precondition>,

54

55

      /// The tasks that must be executed before this task can be executed

56

      #[serde(default)]

57

      pub depends_on: Vec<TaskDependency>,

58

59

      /// The labels for the task

60

      #[serde(default)]

61

      pub labels: HashMap<String, String>,

62

63

      /// The description of the task

64

      #[serde(default)]

65

      pub description: String,

66

67

      /// The environment variables to set before running the task

68

      #[serde(default, deserialize_with = "deserialize_environment")]

69

      pub environment: HashMap<String, String>,

70

71

      /// The environment files to load before running the task

72

      #[serde(default)]

73

      pub env_file: Vec<String>,

74

75

      /// The shell to use when running the task

76

      #[serde(default)]

77

      pub shell: Option<String>,

78

79

      /// Ignore errors if the task fails

80

      #[serde(default)]

81

      pub ignore_errors: Option<bool>,

82

83

      /// Show verbose output

84

      #[serde(default)]

85

      pub verbose: Option<bool>,

86

    }

87

88

    #[derive(Debug, Deserialize)]

89

    #[serde(untagged)]

90

    pub enum Task {

91

      String(String),

92

      Task(Box<TaskArgs>),

93

    }

94

95
99

    impl Task {

96
113

      pub fn run(&self, context: &mut TaskContext) -> anyhow::Result<()> {

97
14

        match self {

98
99

          Task::String(command) => self.execute(context, command),

99
14

          Task::Task(args) => args.run(context),

100
99

        }

101
14

      }

102

103

      fn execute(&self, context: &TaskContext, command: &str) -> anyhow::Result<()> {

104

        assert!(!command.is_empty());

105

106

        let ignore_errors = context.ignore_errors();

107

        let verbose = context.verbose();

108

        let shell: &str = &context.shell();

109

110

        let stdout = get_output_handler(verbose);

111

        let stderr = get_output_handler(verbose);

112

113

        let mut cmd = ProcessCommand::new(shell);

114

        cmd.arg("-c").arg(command).stdout(stdout).stderr(stderr);

115

116

        // Inject environment variables

117

        for (key, value) in context.env_vars.iter() {

118

          cmd.env(key, value);

119

        }

120

121

        let mut cmd = cmd.spawn()?;

122

        if verbose {

123

          handle_output!(cmd.stdout, context);

124

          handle_output!(cmd.stderr, context);

125

        }

126

127

        let status = cmd.wait()?;

128

        if !status.success() && !ignore_errors {

129

          anyhow::bail!("Command failed - {}", command);

130

        }

131

132

        Ok(())

133

      }

134

    }

135

136
99

    impl TaskArgs {

137
113

      pub fn run(&self, context: &mut TaskContext) -> anyhow::Result<()> {

138
14

        assert!(!self.commands.is_empty());

139
99

140
113

        let started = Instant::now();

141
14

        let tick_interval = Duration::from_millis(80);

142
99

143
14

        if let Some(shell) = &self.shell {

144

          let shell: &str = shell;

145
99

          context.set_shell(shell);

146
14

        }

147
99

148
14

        if let Some(ignore_errors) = &self.ignore_errors {

149
99

          context.set_ignore_errors(*ignore_errors);

150
14

        }

151
99

152
14

        if let Some(verbose) = &self.verbose {

153
99

          context.set_verbose(*verbose);

154
14

        }

155

156
99

        // Load environment variables from the task environment and env files field

157
113

        let defined_env = self.load_env()?;

158
14

        let additional_env = self.load_env_file()?;

159
99

160
113

        context.extend_env_vars(defined_env);

161
113

        context.extend_env_vars(additional_env);

162
113

163
14

        let mut rng = rand::thread_rng();

164

        // Spinners can be found here:

165
99

        // https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json

166
113

        let pb_style =

167
113

          ProgressStyle::with_template("{spinner:.green} [{prefix:.bold.dim}] {wide_msg:.cyan/blue} ")?

168
113

            .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⦿");

169
113

170
113

        let depends_on_pb = context.multi.add(ProgressBar::new(self.depends_on.len() as u64));

171
113

172
14

        if !self.depends_on.is_empty() {

173

          depends_on_pb.set_style(pb_style.clone());

174

          depends_on_pb.set_message("Running task dependencies...");

175

          depends_on_pb.enable_steady_tick(tick_interval);

176

          for (i, dependency) in self.depends_on.iter().enumerate() {

177

            thread::sleep(Duration::from_millis(rng.gen_range(40..300)));

178

            depends_on_pb.set_prefix(format!("{}/{}", i + 1, self.depends_on.len()));

179

            dependency.run(context)?;

180

            depends_on_pb.inc(1);

181

          }

182

183

          let message = format!("Dependencies completed in {}.", HumanDuration(started.elapsed()));

184

          if context.is_nested {

185

            depends_on_pb.finish_and_clear();

186

          } else {

187

            depends_on_pb.finish_with_message(message);

188
99

          }

189
14

        }

190
99

191
113

        let precondition_pb = context

192
113

          .multi

193
113

          .add(ProgressBar::new(self.preconditions.len() as u64));

194
113

195
14

        if !self.preconditions.is_empty() {

196

          precondition_pb.set_style(pb_style.clone());

197

          precondition_pb.set_message("Running task precondition...");

198

          precondition_pb.enable_steady_tick(tick_interval);

199

          for (i, precondition) in self.preconditions.iter().enumerate() {

200

            thread::sleep(Duration::from_millis(rng.gen_range(40..300)));

201

            precondition_pb.set_prefix(format!("{}/{}", i + 1, self.preconditions.len()));

202

            precondition.execute(context)?;

203

            precondition_pb.inc(1);

204

          }

205

206

          let message = format!("Preconditions completed in {}.", HumanDuration(started.elapsed()));

207

          if context.is_nested {

208

            precondition_pb.finish_and_clear();

209

          } else {

210

            precondition_pb.finish_with_message(message);

211
99

          }

212
14

        }

213
99

214
113

        let command_pb = context.multi.add(ProgressBar::new(self.commands.len() as u64));

215
113

        command_pb.set_style(pb_style);

216
113

        command_pb.set_message("Running task command...");

217
113

        command_pb.enable_steady_tick(tick_interval);

218
113

        for (i, command) in self.commands.iter().enumerate() {

219
113

          thread::sleep(Duration::from_millis(rng.gen_range(100..400)));

220
113

          command_pb.set_prefix(format!("{}/{}", i + 1, self.commands.len()));

221
113

          command.execute(context)?;

222
14

          command_pb.inc(1);

223

        }

224
99

225
113

        let message = format!("Commands completed in {}.", HumanDuration(started.elapsed()));

226
32

        if context.is_nested {

227
101

          command_pb.finish_and_clear();

228
95

        } else {

229
93

          command_pb.finish_with_message(message);

230
12

        }

231
99

232
113

        Ok(())

233
14

      }

234
99

235
113

      fn load_env(&self) -> anyhow::Result<HashMap<String, String>> {

236
113

        let mut local_env: HashMap<String, String> = HashMap::new();

237
14

        for (key, value) in &self.environment {

238

          let value = self.get_env_value(value)?;

239

          local_env.insert(key.clone(), value);

240

        }

241
99

242
113

        Ok(local_env)

243
14

      }

244
99

245
113

      fn load_env_file(&self) -> anyhow::Result<HashMap<String, String>> {

246
113

        let mut local_env: HashMap<String, String> = HashMap::new();

247
14

        for env_file in &self.env_file {

248

          let contents =

249

            fs::read_to_string(env_file).with_context(|| format!("Failed to read env file - {}", env_file))?;

250

251

          for line in contents.lines() {

252

            if let Some((key, value)) = line.split_once('=') {

253

              local_env.insert(key.trim().to_string(), value.trim().to_string());

254

            }

255

          }

256

        }

257
99

258
113

        Ok(local_env)

259
14

      }

260

261

      fn get_env_value(&self, value_in: &str) -> anyhow::Result<String> {

262

        if is_shell_command(value_in)? {

263

          let verbose = self.verbose();

264

          let shell: &str = &self.shell();

265

          let output = run_shell_command!(value_in, shell, verbose);

266

          Ok(output)

267

        } else {

268

          Ok(value_in.to_string())

269

        }

270

      }

271

272

      fn shell(&self) -> String {

273

        self.shell.clone().unwrap_or(default_shell())

274

      }

275

276

      fn verbose(&self) -> bool {

277

        self.verbose.unwrap_or(default_verbose())

278

      }

279

    }

280

281

    #[cfg(test)]

282

    mod test {

283

      use super::*;

284

285
18

      #[test]

286
20

      fn test_task_1() -> anyhow::Result<()> {

287
20

        {

288
20

          let yaml = "

289
20

            commands:

290
20

              - command: echo \"Hello, World!\"

291
20

                ignore_errors: false

292
20

                verbose: false

293
20

            depends_on:

294
20

              - name: task1

295
20

            description: This is a task

296
20

            environment:

297
20

              FOO: bar

298
20

            env_file:

299
20

              - test.env

300
20

              - test2.env

301
2

          ";

302
18

303
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

304
18

305
20

          if let Task::Task(task) = &task {

306
20

            if let CommandRunner::LocalRun(local_run) = &task.commands[0] {

307
20

              assert_eq!(local_run.command, "echo \"Hello, World!\"");

308
20

              assert_eq!(local_run.work_dir, None);

309
20

              assert_eq!(local_run.shell, "sh");

310
20

              assert_eq!(local_run.ignore_errors, Some(false));

311
2

              assert_eq!(local_run.verbose, Some(false));

312

            }

313
18

314
20

            if let TaskDependency::TaskDependency(args) = &task.depends_on[0] {

315
2

              assert_eq!(args.name, "task1");

316

            }

317
18

318
20

            assert_eq!(task.labels.len(), 0);

319
20

            assert_eq!(task.description, "This is a task");

320
20

            assert_eq!(task.environment.len(), 1);

321
2

            assert_eq!(task.env_file.len(), 2);

322

          } else {

323

            panic!("Expected Task::Task");

324

          }

325
18

326
2

          Ok(())

327
18

        }

328
2

      }

329

330
18

      #[test]

331
20

      fn test_task_2() -> anyhow::Result<()> {

332
20

        {

333
20

          let yaml = "

334
20

            commands:

335
20

              - command: echo 'Hello, World!'

336
20

                ignore_errors: false

337
20

                verbose: false

338
20

            description: This is a task

339
20

            environment:

340
20

              FOO: bar

341
20

              BAR: foo

342
2

          ";

343
18

344
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

345
18

346
20

          if let Task::Task(task) = &task {

347
20

            if let CommandRunner::LocalRun(local_run) = &task.commands[0] {

348
20

              assert_eq!(local_run.command, "echo 'Hello, World!'");

349
20

              assert_eq!(local_run.work_dir, None);

350
20

              assert_eq!(local_run.shell, "sh");

351
20

              assert_eq!(local_run.ignore_errors, Some(false));

352
2

              assert_eq!(local_run.verbose, Some(false));

353

            }

354
18

355
20

            assert_eq!(task.description, "This is a task");

356
20

            assert_eq!(task.depends_on.len(), 0);

357
20

            assert_eq!(task.labels.len(), 0);

358
20

            assert_eq!(task.env_file.len(), 0);

359
2

            assert_eq!(task.environment.len(), 2);

360

          } else {

361

            panic!("Expected Task::Task");

362

          }

363
18

364
2

          Ok(())

365
18

        }

366
2

      }

367

368
18

      #[test]

369
20

      fn test_task_3() -> anyhow::Result<()> {

370
20

        {

371
20

          let yaml = "

372
20

            commands:

373
20

              - command: echo 'Hello, World!'

374
2

          ";

375
18

376
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

377
18

378
20

          if let Task::Task(task) = &task {

379
20

            if let CommandRunner::LocalRun(local_run) = &task.commands[0] {

380
20

              assert_eq!(local_run.command, "echo 'Hello, World!'");

381
20

              assert_eq!(local_run.work_dir, None);

382
20

              assert_eq!(local_run.shell, "sh");

383
20

              assert_eq!(local_run.ignore_errors, None);

384
2

              assert_eq!(local_run.verbose, None);

385

            }

386
18

387
20

            assert_eq!(task.description.len(), 0);

388
20

            assert_eq!(task.depends_on.len(), 0);

389
20

            assert_eq!(task.labels.len(), 0);

390
20

            assert_eq!(task.env_file.len(), 0);

391
2

            assert_eq!(task.environment.len(), 0);

392

          } else {

393

            panic!("Expected Task::Task");

394

          }

395
18

396
2

          Ok(())

397
18

        }

398
2

      }

399

400
18

      #[test]

401
20

      fn test_task_4() -> anyhow::Result<()> {

402
20

        {

403
20

          let yaml = "

404
20

            commands:

405
20

              - container_command:

406
20

                  - echo

407
20

                  - Hello, World!

408
20

                image: docker.io/library/hello-world:latest

409
2

          ";

410
18

411
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

412
18

413
20

          if let Task::Task(task) = &task {

414
20

            if let CommandRunner::ContainerRun(container_run) = &task.commands[0] {

415
20

              assert_eq!(container_run.container_command.len(), 2);

416
20

              assert_eq!(container_run.container_command[0], "echo");

417
20

              assert_eq!(container_run.container_command[1], "Hello, World!");

418
20

              assert_eq!(container_run.image, "docker.io/library/hello-world:latest");

419
20

              assert_eq!(container_run.mounted_paths, Vec::<String>::new());

420
20

              assert_eq!(container_run.ignore_errors, None);

421
2

              assert_eq!(container_run.verbose, None);

422

            }

423
18

424
20

            assert_eq!(task.description.len(), 0);

425
20

            assert_eq!(task.depends_on.len(), 0);

426
20

            assert_eq!(task.labels.len(), 0);

427
20

            assert_eq!(task.env_file.len(), 0);

428
2

            assert_eq!(task.environment.len(), 0);

429

          } else {

430

            panic!("Expected Task::Task");

431

          }

432
18

433
2

          Ok(())

434
18

        }

435
2

      }

436

437
18

      #[test]

438
20

      fn test_task_5() -> anyhow::Result<()> {

439
20

        {

440
20

          let yaml = "

441
20

            commands:

442
20

              - container_command:

443
20

                  - echo

444
20

                  - Hello, World!

445
20

                image: docker.io/library/hello-world:latest

446
20

                mounted_paths:

447
20

                  - /tmp

448
20

                  - /var/tmp

449
2

          ";

450
18

451
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

452
18

453
20

          if let Task::Task(task) = &task {

454
20

            if let CommandRunner::ContainerRun(container_run) = &task.commands[0] {

455
20

              assert_eq!(container_run.container_command.len(), 2);

456
20

              assert_eq!(container_run.container_command[0], "echo");

457
20

              assert_eq!(container_run.container_command[1], "Hello, World!");

458
20

              assert_eq!(container_run.image, "docker.io/library/hello-world:latest");

459
20

              assert_eq!(container_run.mounted_paths, vec!["/tmp", "/var/tmp"]);

460
20

              assert_eq!(container_run.ignore_errors, None);

461
2

              assert_eq!(container_run.verbose, None);

462

            }

463
18

464
20

            assert_eq!(task.description.len(), 0);

465
20

            assert_eq!(task.depends_on.len(), 0);

466
20

            assert_eq!(task.labels.len(), 0);

467
20

            assert_eq!(task.env_file.len(), 0);

468
2

            assert_eq!(task.environment.len(), 0);

469

          } else {

470

            panic!("Expected Task::Task");

471

          }

472
18

473
2

          Ok(())

474
18

        }

475
2

      }

476

477
18

      #[test]

478
20

      fn test_task_6() -> anyhow::Result<()> {

479
20

        {

480
20

          let yaml = "

481
20

            commands:

482
20

              - container_command:

483
20

                  - echo

484
20

                  - Hello, World!

485
20

                image: docker.io/library/hello-world:latest

486
20

                mounted_paths:

487
20

                  - /tmp

488
20

                  - /var/tmp

489
20

                ignore_errors: true

490
2

          ";

491
18

492
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

493
18

494
20

          if let Task::Task(task) = &task {

495
20

            if let CommandRunner::ContainerRun(container_run) = &task.commands[0] {

496
20

              assert_eq!(container_run.container_command.len(), 2);

497
20

              assert_eq!(container_run.container_command[0], "echo");

498
20

              assert_eq!(container_run.container_command[1], "Hello, World!");

499
20

              assert_eq!(container_run.image, "docker.io/library/hello-world:latest");

500
20

              assert_eq!(container_run.mounted_paths, vec!["/tmp", "/var/tmp"]);

501
20

              assert_eq!(container_run.ignore_errors, Some(true));

502
2

              assert_eq!(container_run.verbose, None);

503

            }

504
18

505
20

            assert_eq!(task.description.len(), 0);

506
20

            assert_eq!(task.depends_on.len(), 0);

507
20

            assert_eq!(task.labels.len(), 0);

508
20

            assert_eq!(task.env_file.len(), 0);

509
2

            assert_eq!(task.environment.len(), 0);

510

          } else {

511

            panic!("Expected Task::Task");

512

          }

513
18

514
2

          Ok(())

515
18

        }

516
2

      }

517

518
18

      #[test]

519
20

      fn test_task_7() -> anyhow::Result<()> {

520
20

        {

521
20

          let yaml = "

522
20

            commands:

523
20

              - container_command:

524
20

                  - echo

525
20

                  - Hello, World!

526
20

                image: docker.io/library/hello-world:latest

527
20

                verbose: false

528
2

          ";

529
18

530
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

531
18

532
20

          if let Task::Task(task) = &task {

533
20

            if let CommandRunner::ContainerRun(container_run) = &task.commands[0] {

534
20

              assert_eq!(container_run.container_command.len(), 2);

535
20

              assert_eq!(container_run.container_command[0], "echo");

536
20

              assert_eq!(container_run.container_command[1], "Hello, World!");

537
20

              assert_eq!(container_run.image, "docker.io/library/hello-world:latest");

538
20

              assert_eq!(container_run.mounted_paths, Vec::<String>::new());

539
20

              assert_eq!(container_run.ignore_errors, None);

540
2

              assert_eq!(container_run.verbose, Some(false));

541

            }

542
18

543
20

            assert_eq!(task.description.len(), 0);

544
20

            assert_eq!(task.depends_on.len(), 0);

545
20

            assert_eq!(task.labels.len(), 0);

546
20

            assert_eq!(task.env_file.len(), 0);

547
2

            assert_eq!(task.environment.len(), 0);

548

          } else {

549

            panic!("Expected Task::Task");

550

          }

551
18

552
2

          Ok(())

553
18

        }

554
2

      }

555

556
18

      #[test]

557
20

      fn test_task_8() -> anyhow::Result<()> {

558
20

        {

559
20

          let yaml = "

560
20

            commands:

561
20

              - task: task1

562
2

          ";

563
18

564
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

565
18

566
20

          if let Task::Task(task) = &task {

567
20

            if let CommandRunner::TaskRun(task_run) = &task.commands[0] {

568
20

              assert_eq!(task_run.task, "task1");

569
20

              assert_eq!(task_run.ignore_errors, None);

570
2

              assert_eq!(task_run.verbose, None);

571

            }

572
18

573
20

            assert_eq!(task.description.len(), 0);

574
20

            assert_eq!(task.depends_on.len(), 0);

575
20

            assert_eq!(task.labels.len(), 0);

576
20

            assert_eq!(task.env_file.len(), 0);

577
2

            assert_eq!(task.environment.len(), 0);

578

          } else {

579

            panic!("Expected Task::Task");

580

          }

581
18

582
2

          Ok(())

583
18

        }

584
2

      }

585

586
18

      #[test]

587
20

      fn test_task_9() -> anyhow::Result<()> {

588
20

        {

589
20

          let yaml = "

590
20

            commands:

591
20

              - task: task1

592
20

                verbose: true

593
2

          ";

594
18

595
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

596
18

597
20

          if let Task::Task(task) = &task {

598
20

            if let CommandRunner::TaskRun(task_run) = &task.commands[0] {

599
20

              assert_eq!(task_run.task, "task1");

600
20

              assert_eq!(task_run.ignore_errors, None);

601
2

              assert_eq!(task_run.verbose, Some(true));

602

            }

603
18

604
20

            assert_eq!(task.description.len(), 0);

605
20

            assert_eq!(task.depends_on.len(), 0);

606
20

            assert_eq!(task.labels.len(), 0);

607
20

            assert_eq!(task.env_file.len(), 0);

608
2

            assert_eq!(task.environment.len(), 0);

609

          } else {

610

            panic!("Expected Task::Task");

611

          }

612
18

613
2

          Ok(())

614
18

        }

615
2

      }

616

617
18

      #[test]

618
20

      fn test_task_10() -> anyhow::Result<()> {

619
20

        {

620
20

          let yaml = "

621
20

            commands:

622
20

              - task: task1

623
20

                ignore_errors: true

624
2

          ";

625
18

626
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

627
18

628
20

          if let Task::Task(task) = &task {

629
20

            if let CommandRunner::TaskRun(task_run) = &task.commands[0] {

630
20

              assert_eq!(task_run.task, "task1");

631
20

              assert_eq!(task_run.ignore_errors, Some(true));

632
2

              assert_eq!(task_run.verbose, None);

633

            }

634
18

635
20

            assert_eq!(task.description.len(), 0);

636
20

            assert_eq!(task.depends_on.len(), 0);

637
20

            assert_eq!(task.labels.len(), 0);

638
20

            assert_eq!(task.env_file.len(), 0);

639
2

            assert_eq!(task.environment.len(), 0);

640

          } else {

641

            panic!("Expected Task::Task");

642

          }

643
18

644
2

          Ok(())

645
18

        }

646
2

      }

647

648
18

      #[test]

649
20

      fn test_task_11() -> anyhow::Result<()> {

650
20

        {

651
20

          let yaml = "

652
20

            echo 'Hello, World!'

653
2

          ";

654
18

655
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

656
18

657
20

          if let Task::String(task) = &task {

658
2

            assert_eq!(task, "echo 'Hello, World!'");

659

          } else {

660

            panic!("Expected Task::String");

661

          }

662
18

663
2

          Ok(())

664
18

        }

665
2

      }

666

667
18

      #[test]

668
20

      fn test_task_12() -> anyhow::Result<()> {

669
20

        {

670
20

          let yaml = "

671
20

            'true'

672
2

          ";

673
18

674
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

675
18

676
20

          if let Task::String(task) = &task {

677
2

            assert_eq!(task, "true");

678

          } else {

679

            panic!("Expected Task::String");

680

          }

681
18

682
2

          Ok(())

683
18

        }

684
2

      }

685

686

      #[test]

687
2

      fn test_task_13() -> anyhow::Result<()> {

688
2

        {

689
2

          let yaml = "

690
2

            commands: []

691
2

            environment:

692
2

              FOO: bar

693
2

              BAR: foo

694
2

              KEY: 42

695
2

              PIS: 3.14

696
2

          ";

697

698
2

          let task = serde_yaml::from_str::<Task>(yaml)?;

699

700
2

          if let Task::Task(task) = &task {

701
2

            assert_eq!(task.environment.len(), 4);

702
2

            assert_eq!(task.environment.get("FOO").unwrap(), "bar");

703
2

            assert_eq!(task.environment.get("BAR").unwrap(), "foo");

704
2

            assert_eq!(task.environment.get("KEY").unwrap(), "42");

705

          } else {

706

            panic!("Expected Task::Task");

707

          }

708

709
2

          Ok(())

710

        }

711
2

      }

712

    }