aboutsummaryrefslogtreecommitdiff
path: root/build2/test/common.cxx
blob: 8197d5d5a4126d5bd6097143fa6b6014ccdea5ac (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
// file      : build2/test/common.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#include <build2/test/common.hxx>

#include <build2/target.hxx>
#include <build2/algorithm.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;
    }
  }
}