1//
2// Automated Testing Framework (atf)
3//
4// Copyright (c) 2007 The NetBSD Foundation, Inc.
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions
9// are met:
10// 1. Redistributions of source code must retain the above copyright
11//    notice, this list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright
13//    notice, this list of conditions and the following disclaimer in the
14//    documentation and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20// IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28//
29
30#include <cassert>
31#include <cstdlib>
32#include <fstream>
33
34#include "atffile.hpp"
35#include "exceptions.hpp"
36#include "expand.hpp"
37#include "parser.hpp"
38
39namespace impl = tools;
40namespace detail = tools::detail;
41
42namespace {
43
44typedef std::map< std::string, std::string > vars_map;
45
46} // anonymous namespace
47
48// ------------------------------------------------------------------------
49// The "atf_atffile" auxiliary parser.
50// ------------------------------------------------------------------------
51
52namespace atf_atffile {
53
54static const tools::parser::token_type eof_type = 0;
55static const tools::parser::token_type nl_type = 1;
56static const tools::parser::token_type text_type = 2;
57static const tools::parser::token_type colon_type = 3;
58static const tools::parser::token_type conf_type = 4;
59static const tools::parser::token_type dblquote_type = 5;
60static const tools::parser::token_type equal_type = 6;
61static const tools::parser::token_type hash_type = 7;
62static const tools::parser::token_type prop_type = 8;
63static const tools::parser::token_type tp_type = 9;
64static const tools::parser::token_type tp_glob_type = 10;
65
66class tokenizer : public tools::parser::tokenizer< std::istream > {
67public:
68    tokenizer(std::istream& is, size_t curline) :
69        tools::parser::tokenizer< std::istream >
70            (is, true, eof_type, nl_type, text_type, curline)
71    {
72        add_delim(':', colon_type);
73        add_delim('=', equal_type);
74        add_delim('#', hash_type);
75        add_quote('"', dblquote_type);
76        add_keyword("conf", conf_type);
77        add_keyword("prop", prop_type);
78        add_keyword("tp", tp_type);
79        add_keyword("tp-glob", tp_glob_type);
80    }
81};
82
83} // namespace atf_atffile
84
85// ------------------------------------------------------------------------
86// The "atf_atffile_reader" class.
87// ------------------------------------------------------------------------
88
89detail::atf_atffile_reader::atf_atffile_reader(std::istream& is) :
90    m_is(is)
91{
92}
93
94detail::atf_atffile_reader::~atf_atffile_reader(void)
95{
96}
97
98void
99detail::atf_atffile_reader::got_conf(
100    const std::string& name __attribute__((__unused__)),
101    const std::string& val __attribute__((__unused__)))
102{
103}
104
105void
106detail::atf_atffile_reader::got_prop(
107    const std::string& name __attribute__((__unused__)),
108    const std::string& val __attribute__((__unused__)))
109{
110}
111
112void
113detail::atf_atffile_reader::got_tp(
114    const std::string& name __attribute__((__unused__)),
115    bool isglob __attribute__((__unused__)))
116{
117}
118
119void
120detail::atf_atffile_reader::got_eof(void)
121{
122}
123
124void
125detail::atf_atffile_reader::read(void)
126{
127    using tools::parser::parse_error;
128    using namespace atf_atffile;
129
130    std::pair< size_t, tools::parser::headers_map > hml =
131        tools::parser::read_headers(m_is, 1);
132    tools::parser::validate_content_type(hml.second,
133        "application/X-atf-atffile", 1);
134
135    tokenizer tkz(m_is, hml.first);
136    tools::parser::parser< tokenizer > p(tkz);
137
138    for (;;) {
139        try {
140            tools::parser::token t =
141                p.expect(conf_type, hash_type, prop_type, tp_type,
142                         tp_glob_type, nl_type, eof_type,
143                         "conf, #, prop, tp, tp-glob, a new line or eof");
144            if (t.type() == eof_type)
145                break;
146
147            if (t.type() == conf_type) {
148                t = p.expect(colon_type, "`:'");
149
150                t = p.expect(text_type, "variable name");
151                std::string var = t.text();
152
153                t = p.expect(equal_type, "equal sign");
154
155                t = p.expect(text_type, "word or quoted string");
156                ATF_PARSER_CALLBACK(p, got_conf(var, t.text()));
157            } else if (t.type() == hash_type) {
158                (void)p.rest_of_line();
159            } else if (t.type() == prop_type) {
160                t = p.expect(colon_type, "`:'");
161
162                t = p.expect(text_type, "property name");
163                std::string name = t.text();
164
165                t = p.expect(equal_type, "equale sign");
166
167                t = p.expect(text_type, "word or quoted string");
168                ATF_PARSER_CALLBACK(p, got_prop(name, t.text()));
169            } else if (t.type() == tp_type) {
170                t = p.expect(colon_type, "`:'");
171
172                t = p.expect(text_type, "word or quoted string");
173                ATF_PARSER_CALLBACK(p, got_tp(t.text(), false));
174            } else if (t.type() == tp_glob_type) {
175                t = p.expect(colon_type, "`:'");
176
177                t = p.expect(text_type, "word or quoted string");
178                ATF_PARSER_CALLBACK(p, got_tp(t.text(), true));
179            } else if (t.type() == nl_type) {
180                continue;
181            } else
182                std::abort();
183
184            t = p.expect(nl_type, hash_type, eof_type,
185                         "new line or comment");
186            if (t.type() == hash_type) {
187                (void)p.rest_of_line();
188                t = p.next();
189            } else if (t.type() == eof_type)
190                break;
191        } catch (const parse_error& pe) {
192            p.add_error(pe);
193            p.reset(nl_type);
194        }
195    }
196
197    ATF_PARSER_CALLBACK(p, got_eof());
198}
199
200// ------------------------------------------------------------------------
201// The "reader" helper class.
202// ------------------------------------------------------------------------
203
204class reader : public detail::atf_atffile_reader {
205    const tools::fs::directory& m_dir;
206    vars_map m_conf, m_props;
207    std::vector< std::string > m_tps;
208
209    void
210    got_tp(const std::string& name, bool isglob)
211    {
212        if (isglob) {
213            std::vector< std::string > ms =
214                tools::expand::expand_glob(name, m_dir.names());
215            // Cannot use m_tps.insert(iterator, begin, end) here because it
216            // does not work under Solaris.
217            for (std::vector< std::string >::const_iterator iter = ms.begin();
218                 iter != ms.end(); iter++)
219                m_tps.push_back(*iter);
220        } else {
221            if (m_dir.find(name) == m_dir.end())
222                throw tools::not_found_error< tools::fs::path >
223                    ("Cannot locate the " + name + " file",
224                     tools::fs::path(name));
225            m_tps.push_back(name);
226        }
227    }
228
229    void
230    got_prop(const std::string& name, const std::string& val)
231    {
232        m_props[name] = val;
233    }
234
235    void
236    got_conf(const std::string& var, const std::string& val)
237    {
238        m_conf[var] = val;
239    }
240
241public:
242    reader(std::istream& is, const tools::fs::directory& dir) :
243        detail::atf_atffile_reader(is),
244        m_dir(dir)
245    {
246    }
247
248    const vars_map&
249    conf(void)
250        const
251    {
252        return m_conf;
253    }
254
255    const vars_map&
256    props(void)
257        const
258    {
259        return m_props;
260    }
261
262    const std::vector< std::string >&
263    tps(void)
264        const
265    {
266        return m_tps;
267    }
268};
269
270// ------------------------------------------------------------------------
271// The "atffile" class.
272// ------------------------------------------------------------------------
273
274impl::atffile::atffile(const vars_map& config_vars,
275                       const std::vector< std::string >& test_program_names,
276                       const vars_map& properties) :
277    m_conf(config_vars),
278    m_tps(test_program_names),
279    m_props(properties)
280{
281    assert(properties.find("test-suite") != properties.end());
282}
283
284const std::vector< std::string >&
285impl::atffile::tps(void)
286    const
287{
288    return m_tps;
289}
290
291const vars_map&
292impl::atffile::conf(void)
293    const
294{
295    return m_conf;
296}
297
298const vars_map&
299impl::atffile::props(void)
300    const
301{
302    return m_props;
303}
304
305// ------------------------------------------------------------------------
306// Free functions.
307// ------------------------------------------------------------------------
308
309// XXX Glob expansion and file existance checks certainly do not belong in
310// a *parser*.  This needs to be taken out...
311impl::atffile
312impl::read_atffile(const tools::fs::path& filename)
313{
314    // Scan the directory where the atffile lives in to gather a list of
315    // all possible test programs in it.
316    tools::fs::directory dir(filename.branch_path());
317    dir.erase(filename.leaf_name());
318    tools::fs::directory::iterator iter = dir.begin();
319    while (iter != dir.end()) {
320        const std::string& name = (*iter).first;
321        const tools::fs::file_info& fi = (*iter).second;
322
323        // Discard hidden files and non-executable ones so that they are
324        // not candidates for glob matching.
325        if (name[0] == '.' || (!fi.is_owner_executable() &&
326                               !fi.is_group_executable()))
327            dir.erase(iter++);
328        else
329            iter++;
330    }
331
332    // Parse the atffile.
333    std::ifstream is(filename.c_str());
334    if (!is)
335        throw tools::not_found_error< tools::fs::path >
336            ("Cannot open Atffile", filename);
337    reader r(is, dir);
338    r.read();
339    is.close();
340
341    // Sanity checks.
342    if (r.props().find("test-suite") == r.props().end())
343        throw tools::not_found_error< std::string >
344            ("Undefined property `test-suite'", "test-suite");
345
346    return atffile(r.conf(), r.tps(), r.props());
347}
348