1//
2// Automated Testing Framework (atf)
3//
4// Copyright (c) 2007, 2008, 2010 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 <fstream>
31
32#include "atf-c++/detail/exceptions.hpp"
33#include "atf-c++/detail/expand.hpp"
34#include "atf-c++/detail/parser.hpp"
35#include "atf-c++/detail/sanity.hpp"
36
37#include "atffile.hpp"
38
39namespace impl = atf::atf_run;
40namespace detail = atf::atf_run::detail;
41
42// ------------------------------------------------------------------------
43// The "atf_atffile" auxiliary parser.
44// ------------------------------------------------------------------------
45
46namespace atf_atffile {
47
48static const atf::parser::token_type eof_type = 0;
49static const atf::parser::token_type nl_type = 1;
50static const atf::parser::token_type text_type = 2;
51static const atf::parser::token_type colon_type = 3;
52static const atf::parser::token_type conf_type = 4;
53static const atf::parser::token_type dblquote_type = 5;
54static const atf::parser::token_type equal_type = 6;
55static const atf::parser::token_type hash_type = 7;
56static const atf::parser::token_type prop_type = 8;
57static const atf::parser::token_type tp_type = 9;
58static const atf::parser::token_type tp_glob_type = 10;
59
60class tokenizer : public atf::parser::tokenizer< std::istream > {
61public:
62    tokenizer(std::istream& is, size_t curline) :
63        atf::parser::tokenizer< std::istream >
64            (is, true, eof_type, nl_type, text_type, curline)
65    {
66        add_delim(':', colon_type);
67        add_delim('=', equal_type);
68        add_delim('#', hash_type);
69        add_quote('"', dblquote_type);
70        add_keyword("conf", conf_type);
71        add_keyword("prop", prop_type);
72        add_keyword("tp", tp_type);
73        add_keyword("tp-glob", tp_glob_type);
74    }
75};
76
77} // namespace atf_atffile
78
79// ------------------------------------------------------------------------
80// The "atf_atffile_reader" class.
81// ------------------------------------------------------------------------
82
83detail::atf_atffile_reader::atf_atffile_reader(std::istream& is) :
84    m_is(is)
85{
86}
87
88detail::atf_atffile_reader::~atf_atffile_reader(void)
89{
90}
91
92void
93detail::atf_atffile_reader::got_conf(const std::string& name,
94                                     const std::string& val)
95{
96}
97
98void
99detail::atf_atffile_reader::got_prop(const std::string& name,
100                                     const std::string& val)
101{
102}
103
104void
105detail::atf_atffile_reader::got_tp(const std::string& name, bool isglob)
106{
107}
108
109void
110detail::atf_atffile_reader::got_eof(void)
111{
112}
113
114void
115detail::atf_atffile_reader::read(void)
116{
117    using atf::parser::parse_error;
118    using namespace atf_atffile;
119
120    std::pair< size_t, atf::parser::headers_map > hml =
121        atf::parser::read_headers(m_is, 1);
122    atf::parser::validate_content_type(hml.second,
123        "application/X-atf-atffile", 1);
124
125    tokenizer tkz(m_is, hml.first);
126    atf::parser::parser< tokenizer > p(tkz);
127
128    for (;;) {
129        try {
130            atf::parser::token t =
131                p.expect(conf_type, hash_type, prop_type, tp_type,
132                         tp_glob_type, nl_type, eof_type,
133                         "conf, #, prop, tp, tp-glob, a new line or eof");
134            if (t.type() == eof_type)
135                break;
136
137            if (t.type() == conf_type) {
138                t = p.expect(colon_type, "`:'");
139
140                t = p.expect(text_type, "variable name");
141                std::string var = t.text();
142
143                t = p.expect(equal_type, "equal sign");
144
145                t = p.expect(text_type, "word or quoted string");
146                ATF_PARSER_CALLBACK(p, got_conf(var, t.text()));
147            } else if (t.type() == hash_type) {
148                (void)p.rest_of_line();
149            } else if (t.type() == prop_type) {
150                t = p.expect(colon_type, "`:'");
151
152                t = p.expect(text_type, "property name");
153                std::string name = t.text();
154
155                t = p.expect(equal_type, "equale sign");
156
157                t = p.expect(text_type, "word or quoted string");
158                ATF_PARSER_CALLBACK(p, got_prop(name, t.text()));
159            } else if (t.type() == tp_type) {
160                t = p.expect(colon_type, "`:'");
161
162                t = p.expect(text_type, "word or quoted string");
163                ATF_PARSER_CALLBACK(p, got_tp(t.text(), false));
164            } else if (t.type() == tp_glob_type) {
165                t = p.expect(colon_type, "`:'");
166
167                t = p.expect(text_type, "word or quoted string");
168                ATF_PARSER_CALLBACK(p, got_tp(t.text(), true));
169            } else if (t.type() == nl_type) {
170                continue;
171            } else
172                UNREACHABLE;
173
174            t = p.expect(nl_type, hash_type, eof_type,
175                         "new line or comment");
176            if (t.type() == hash_type) {
177                (void)p.rest_of_line();
178                t = p.next();
179            } else if (t.type() == eof_type)
180                break;
181        } catch (const parse_error& pe) {
182            p.add_error(pe);
183            p.reset(nl_type);
184        }
185    }
186
187    ATF_PARSER_CALLBACK(p, got_eof());
188}
189
190// ------------------------------------------------------------------------
191// The "reader" helper class.
192// ------------------------------------------------------------------------
193
194class reader : public detail::atf_atffile_reader {
195    const atf::fs::directory& m_dir;
196    atf::tests::vars_map m_conf, m_props;
197    std::vector< std::string > m_tps;
198
199    void
200    got_tp(const std::string& name, bool isglob)
201    {
202        if (isglob) {
203            std::vector< std::string > ms =
204                atf::expand::expand_glob(name, m_dir.names());
205            // Cannot use m_tps.insert(iterator, begin, end) here because it
206            // does not work under Solaris.
207            for (std::vector< std::string >::const_iterator iter = ms.begin();
208                 iter != ms.end(); iter++)
209                m_tps.push_back(*iter);
210        } else {
211            if (m_dir.find(name) == m_dir.end())
212                throw atf::not_found_error< atf::fs::path >
213                    ("Cannot locate the " + name + " file",
214                     atf::fs::path(name));
215            m_tps.push_back(name);
216        }
217    }
218
219    void
220    got_prop(const std::string& name, const std::string& val)
221    {
222        m_props[name] = val;
223    }
224
225    void
226    got_conf(const std::string& var, const std::string& val)
227    {
228        m_conf[var] = val;
229    }
230
231public:
232    reader(std::istream& is, const atf::fs::directory& dir) :
233        detail::atf_atffile_reader(is),
234        m_dir(dir)
235    {
236    }
237
238    const atf::tests::vars_map&
239    conf(void)
240        const
241    {
242        return m_conf;
243    }
244
245    const atf::tests::vars_map&
246    props(void)
247        const
248    {
249        return m_props;
250    }
251
252    const std::vector< std::string >&
253    tps(void)
254        const
255    {
256        return m_tps;
257    }
258};
259
260// ------------------------------------------------------------------------
261// The "atffile" class.
262// ------------------------------------------------------------------------
263
264impl::atffile::atffile(const atf::tests::vars_map& config_vars,
265                       const std::vector< std::string >& test_program_names,
266                       const atf::tests::vars_map& properties) :
267    m_conf(config_vars),
268    m_tps(test_program_names),
269    m_props(properties)
270{
271    PRE(properties.find("test-suite") != properties.end());
272}
273
274const std::vector< std::string >&
275impl::atffile::tps(void)
276    const
277{
278    return m_tps;
279}
280
281const atf::tests::vars_map&
282impl::atffile::conf(void)
283    const
284{
285    return m_conf;
286}
287
288const atf::tests::vars_map&
289impl::atffile::props(void)
290    const
291{
292    return m_props;
293}
294
295// ------------------------------------------------------------------------
296// Free functions.
297// ------------------------------------------------------------------------
298
299// XXX Glob expansion and file existance checks certainly do not belong in
300// a *parser*.  This needs to be taken out...
301impl::atffile
302impl::read_atffile(const atf::fs::path& filename)
303{
304    // Scan the directory where the atffile lives in to gather a list of
305    // all possible test programs in it.
306    fs::directory dir(filename.branch_path());
307    dir.erase(filename.leaf_name());
308    fs::directory::iterator iter = dir.begin();
309    while (iter != dir.end()) {
310        const std::string& name = (*iter).first;
311        const fs::file_info& fi = (*iter).second;
312
313        // Discard hidden files and non-executable ones so that they are
314        // not candidates for glob matching.
315        if (name[0] == '.' || (!fi.is_owner_executable() &&
316                               !fi.is_group_executable()))
317            dir.erase(iter++);
318        else
319            iter++;
320    }
321
322    // Parse the atffile.
323    std::ifstream is(filename.c_str());
324    if (!is)
325        throw atf::not_found_error< fs::path >
326            ("Cannot open Atffile", filename);
327    reader r(is, dir);
328    r.read();
329    is.close();
330
331    // Sanity checks.
332    if (r.props().find("test-suite") == r.props().end())
333        throw atf::not_found_error< std::string >
334            ("Undefined property `test-suite'", "test-suite");
335
336    return atffile(r.conf(), r.tps(), r.props());
337}
338