aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/test/common.cxx
blob: 7fdb3471a8213ecee9b0b32cc1db8cb62f837250 (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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// file      : libbuild2/test/common.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <libbuild2/test/common.hxx>

#include <libbuild2/target.hxx>
#include <libbuild2/algorithm.hxx>

#include <libbuild2/script/timeout.hxx>

#include <libbuild2/test/module.hxx>

using namespace std;

namespace build2
{
  namespace test
  {
    // Determine if we have the target (first), id path (second), or both (in
    // which case we also advance the iterator).
    //
    static pair<const name*, const name*>
    sense (names::const_iterator& i)
    {
      const name* tn (nullptr);
      const name* pn (nullptr);

      if (i->pair)
      {
        tn = &*i++;
        pn = &*i;
      }
      else
      {
        // If it has a type (exe{hello}) or a directory (basics/), then
        // we assume it is a target.
        //
        (i->typed () || !i->dir.empty () ? tn : pn) = &*i;
      }

      // Validate the target.
      //
      if (tn != nullptr)
      {
        if (tn->qualified ())
          fail << "project-qualified target '" << *tn << " in config.test";
      }

      // Validate the id path.
      //
      if (pn != nullptr)
      {
        if (!pn->simple () || pn->empty ())
          fail << "invalid id path '" << *pn << " in config.test";
      }

      return make_pair (tn, pn);
    }

    bool common::
    pass (const target& a) const
    {
      if (test_ == nullptr)
        return true;

      // We need to "enable" aliases that "lead up" to the targets we are
      // interested in. So see if any target is in a subdirectory of this
      // alias.
      //
      // If we don't see any targets (e.g., only id paths), then we assume all
      // targets match and therefore we always pass.
      //
      bool r (true);

      // Directory part from root to this alias (the same in src and out).
      //
      const dir_path d (a.out_dir ().leaf (root_->out_path ()));

      for (auto i (test_->begin ()); i != test_->end (); ++i)
      {
        if (const name* n = sense (i).first)
        {
          // Reset result to false if no match (but we have seen a target).
          //
          r = n->dir.sub (d);

          // See test() below for details on this special case.
          //
          if (!r && !n->typed ())
            r = d.sub (n->dir);

          if (r)
            break;
        }
      }

      return r;
    }

    bool common::
    test (const target& t) const
    {
      if (test_ == nullptr)
        return true;

      // If we don't see any targets (e.g., only id paths), then we assume
      // all of them match.
      //
      bool r (true);

      // Directory part from root to this alias (the same in src and out).
      //
      const dir_path d (t.out_dir ().leaf (root_->out_path ()));
      const target_type& tt (t.type ());

      for (auto i (test_->begin ()); i != test_->end (); ++i)
      {
        if (const name* n = sense (i).first)
        {
          // Reset result to false if no match (but we have seen a target).
          //

          // When specifying a directory, for example, config.tests=tests/,
          // one would intuitively expect that all the tests under it will
          // run. But that's not what will happen with the below test: while
          // the dir{tests/} itself will match, any target underneath won't.
          // So we are going to handle this type if a target specially by
          // making it match any target in or under it.
          //
          // Note that we only do this for tests/, not dir{tests/} since it is
          // not always the semantics that one wants. Sometimes one may want
          // to run tests (scripts) just for the tests/ target but not for any
          // of its prerequisites. So dir{tests/} is a way to disable this
          // special logic.
          //
          // Note: the same code as in test() below.
          //
          if (!n->typed ())
            r = d.sub (n->dir);
          else
            // First quickly and cheaply weed out names that cannot possibly
            // match. Only then search for a target (as if it was a
            // prerequisite), which can be expensive.
            //
            // We cannot specify an src target in config.test since we used
            // the pair separator for ids. As a result, we search for both
            // out and src targets.
            //
            r =
              t.name == n->value &&                   // Name matches.
              tt.name == n->type &&                   // Target type matches.
              d == n->dir        &&                   // Directory matches.
              (search_existing (*n, *root_)    == &t ||
               search_existing (*n, *root_, d) == &t);

          if (r)
            break;
        }
      }

      return r;
    }

    bool common::
    test (const target& t, const path& id) const
    {
      if (test_ == nullptr)
        return true;

      // If we don't see any id paths (e.g., only targets), then we assume
      // all of them match.
      //
      bool r (true);

      // Directory part from root to this alias (the same in src and out).
      //
      const dir_path d (t.out_dir ().leaf (root_->out_path ()));
      const target_type& tt (t.type ());

      for (auto i (test_->begin ()); i != test_->end (); ++i)
      {
        auto p (sense (i));

        if (const name* n = p.second)
        {
          // If there is a target, check that it matches ours.
          //
          if (const name* n = p.first)
          {
            // Note: the same code as in test() above.
            //
            bool r;

            if (!n->typed ())
              r = d.sub (n->dir);
            else
              r =
                t.name == n->value &&
                tt.name == n->type &&
                d == n->dir        &&
                (search_existing (*n, *root_)    == &t ||
                 search_existing (*n, *root_, d) == &t);

            if (!r)
              continue; // Not our target.
          }

          // If the id (group) "leads up" to what we want to run or we
          // (group) lead up to the id, then match.
          //
          const path p (n->value);

          // Reset result to false if no match (but we have seen an id path).
          //
          if ((r = p.sub (id) || id.sub (p)))
            break;
        }
      }

      return r;
    }

    optional<timestamp> common::
    operation_deadline () const
    {
      if (!operation_timeout)
        return nullopt;

      duration::rep r (operation_deadline_.load (memory_order_consume));

      if (r == timestamp_unknown_rep)
      {
        duration::rep t (timestamp (system_clock::now () + *operation_timeout).
                         time_since_epoch ().count ());

        if (operation_deadline_.compare_exchange_strong (r,
                                                         t,
                                                         memory_order_release,
                                                         memory_order_consume))
          r = t;
      }

      return timestamp (duration (r));
    }

    // Helpers.
    //
    optional<timestamp>
    operation_deadline (const target& t)
    {
      optional<timestamp> r;

      for (const scope* s (t.base_scope ().root_scope ());
           s != nullptr;
           s = s->parent_scope ()->root_scope ())
      {
        if (auto* m = s->find_module<module> (module::name))
          r = earlier (r, m->operation_deadline ());
      }

      return r;
    }

    optional<duration>
    test_timeout (const target& t)
    {
      optional<duration> r;

      for (const scope* s (t.base_scope ().root_scope ());
           s != nullptr;
           s = s->parent_scope ()->root_scope ())
      {
        if (auto* m = s->find_module<module> (module::name))
          r = earlier (r, m->test_timeout);
      }

      return r;
    }

    optional<timestamp>
    test_deadline (const target& t)
    {
      optional<timestamp> r (operation_deadline (t));

      if (optional<duration> d = test_timeout (t))
        r = earlier (r, system_clock::now () + *d);

      return r;
    }
  }
}