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
#include "lix/libexpr/attr-path.hh"
#include "lix/libexpr/attr-set.hh"
#include "tests/libexpr.hh"
#include <gtest/gtest.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <rapidcheck/gen/Arbitrary.h>
#include <rapidcheck/gen/Container.h>
#include <rapidcheck/gen/Predicate.h>
#include <rapidcheck/gtest.h>
#pragma GCC diagnostic pop

namespace nix {

class AttrPathEval : public LibExprTest
{
public:
    std::pair<Value, PosIdx> testFindAlongAttrPath(std::string expr, std::string path);
};

RC_GTEST_PROP(AttrPath, prop_round_trip, ())
{
    auto strings = *rc::gen::container<std::vector<std::string>>(
        rc::gen::container<std::string>(rc::gen::distinctFrom('"'))
    );
    auto const unparsed = unparseAttrPath(strings);
    auto const unparsedReparsed = parseAttrPath(unparsed);

    RC_ASSERT(strings == unparsedReparsed);
}

std::pair<Value, PosIdx> AttrPathEval::testFindAlongAttrPath(std::string expr, std::string path)
{
    auto v = eval(expr);
    auto bindings = evalState().ctx.buildBindings(0).finish();
    return findAlongAttrPath(state, path, *bindings, v);
}

// n.b. I do not know why we throw for empty attrs but they are apparently
// disallowed.
TEST_F(AttrPathEval, emptyAttrsThrowsWithoutQuotes)
{
    std::string expr = "{a.\"\".b = 2;}";
    ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a"));
    ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\".b"));
    ASSERT_THROW(testFindAlongAttrPath(expr, "a..b"), Error);
    ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\""));
}

TEST(attr_path_eval, quotes)
{
    auto p1 = parseAttrPath("foo.\"foo bar\".baz");
    ASSERT_EQ(3, p1.size());
    ASSERT_EQ("foo", p1[0]);
    ASSERT_EQ("foo bar", p1[1]);
    ASSERT_EQ("baz", p1[2]);

    auto p2 = parseAttrPath("foo.\"foo bar\"");
    ASSERT_EQ(2, p2.size());
    ASSERT_EQ("foo", p2[0]);
    ASSERT_EQ("foo bar", p2[1]);

    auto p3 = parseAttrPath("\"foo bar\"");
    ASSERT_EQ(1, p3.size());
    ASSERT_EQ("foo bar", p3[0]);
}

TEST(attr_path_eval, quotes_empty)
{
    auto p1 = parseAttrPath("foo.\"\".bar");
    ASSERT_EQ(3, p1.size());
    ASSERT_EQ("foo", p1[0]);
    ASSERT_EQ("", p1[1]);
    ASSERT_EQ("bar", p1[2]);

    auto p2 = parseAttrPath("foo.\"\"");
    ASSERT_EQ(2, p2.size());
    ASSERT_EQ("foo", p2[0]);
    ASSERT_EQ("", p2[1]);

    auto p3 = parseAttrPath("\"\"");
    ASSERT_EQ(1, p3.size());
    ASSERT_EQ("", p3[0]);
}

TEST(attr_path_eval, quotes_syntax)
{
    ASSERT_THROW(parseAttrPath("foo.\"bar"), ParseError);

    // escaped quotes (\") are not supported
    ASSERT_THROW(parseAttrPath("foo.\"bar\\\"\""), ParseError);
}
}