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
}