aboutsummaryrefslogtreecommitdiff
path: root/build/test/rule.cxx
blob: 91ddb4fcbeffc9132f3edd9453d31626fda93f4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// file      : build/test/rule.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#include <build/test/rule>

#include <butl/process>
#include <butl/fdstream>

#include <build/scope>
#include <build/target>
#include <build/algorithm>
#include <build/diagnostics>

using namespace std;
using namespace butl;

namespace build
{
  namespace test
  {
    match_result rule::
    match (action a, target& t, const std::string&) const
    {
      // First determine if this is a test.
      //
      auto v (t.vars["test"]);

      if (!v)
        v.rebind (t.base_scope ()[string("test.") + t.type ().name]);

      if (!v || !v.as<bool> ())
        return match_result (t, false); // "Not a test" result.

      // If this is the update pre-operation, make someone else do
      // the job.
      //
      if (a.operation () != test_id)
        return nullptr;

      return match_result (t, true);
    }

    recipe rule::
    apply (action a, target&, const match_result& mr) const
    {
      if (!mr.value) // Not a test.
        return noop_recipe;

      return a == action (perform_id, test_id)
        ? &perform_test
        : noop_recipe; // Don't do anything for other meta-operations.
    }

    // The format of args shall be:
    //
    // name1 arg arg ... nullptr
    // name2 arg arg ... nullptr
    // ...
    // nameN arg arg ... nullptr nullptr
    //
    static bool
    pipe_process (char const** args, process* prev = nullptr)
    {
      // Find the next process, if any.
      //
      char const** next (args);
      for (next++; *next != nullptr; next++) ;
      next++;

      // Redirect stdout to a pipe unless we are last, in which
      // case redirect it to stderr.
      //
      int out (*next == nullptr ? 2 : -1);
      bool pr, wr;

      if (prev == nullptr)
      {
        // First process.
        //
        process p (args, 0, out);
        pr = *next == nullptr || pipe_process (next, &p);
        wr = p.wait ();
      }
      else
      {
        // Next process.
        //
        process p (args, *prev, out);
        pr = *next == nullptr || pipe_process (next, &p);
        wr = p.wait ();
      }

      if (!wr)
      {
        // @@ Needs to go into the same diag record.
        //
        error << "non-zero exit status from:";
        print_process (args);
      }

      return pr && wr;
    }


    target_state rule::
    perform_test (action, target& t)
    {
      // @@ Would be nice to print what signal/core was dumped.
      //

      // @@ Doesn't have to be a file target if we have test.cmd.
      //
      file& ft (static_cast<file&> (t));
      assert (!ft.path ().empty ()); // Should have been assigned by update.

      cstrings args {ft.path ().string ().c_str (), nullptr};

      args.push_back ("diff");
      args.push_back ("-u");
      args.push_back ("test.std");
      args.push_back ("-");
      args.push_back (nullptr);

      args.push_back (nullptr); // Second.

      if (verb)
        print_process (args);
      else
        text << "test " << t;

      try
      {
        if (!pipe_process (args.data ()))
        {
          //@@ Need to use the same diag record.
          //
          error << "failed test:";
          print_process (args);
          throw failed ();
        }

        return target_state::changed;
      }
      catch (const process_error& e)
      {
        error << "unable to execute " << args[0] << ": " << e.what ();

        if (e.child ())
          exit (1);

        throw failed ();
      }
    }
  }
}