File: test_rust.py

package info (click to toggle)
glean-parser 15.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,260 kB
  • sloc: python: 7,033; ruby: 100; makefile: 87
file content (291 lines) | stat: -rw-r--r-- 9,129 bytes parent folder | download
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
# -*- coding: utf-8 -*-

# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/

from pathlib import Path
import shutil
import subprocess
import re

from glean_parser import rust
from glean_parser import metrics
from glean_parser import pings
from glean_parser import translate


ROOT = Path(__file__).parent


import pytest
pytest.mark.skip(reason="Skip test in Debian")
def run_linters(files):
    # Syntax check on the generated files.
    # Only run this test if cargo is on the path.
    if shutil.which("rustfmt"):
        for filepath in files:
            subprocess.check_call(
                [
                    "rustfmt",
                    "--check",
                    filepath,
                ]
            )
    if shutil.which("cargo"):
        for filepath in files:
            subprocess.check_call(
                [
                    "cargo",
                    "clippy",
                    "--all",
                    "--",
                    "-D",
                    "warnings",
                    filepath,
                ]
            )


def test_parser(tmp_path):
    """Test translating metrics to Rust files."""
    translate.translate(
        ROOT / "data" / "core.yaml", "rust", tmp_path, {}, {"allow_reserved": True}
    )

    assert set(x.name for x in tmp_path.iterdir()) == set(["glean_metrics.rs"])

    # Make sure descriptions made it in
    with (tmp_path / "glean_metrics.rs").open("r", encoding="utf-8") as fd:
        content = fd.read()

        assert "True if the user has set Firefox as the default browser." in content
        assert "جمع 搜集" in content
        assert 'category: "telemetry"' in content

    # We don't have a cargo.toml, not sure what to do here aside from creating a fake
    # one for the purpose of running cargo fmt and cargo clippy
    # run_linters(tmp_path.glob("*.rs"))

#pytest.mark.skip(reason="Skip test in Debian")
def ping_parser(tmp_path):
    """Test translating pings to Rust files."""
    translate.translate(
        ROOT / "data" / "pings.yaml",
        "rust",
        tmp_path,
        {"namespace": "Foo"},
        {"allow_reserved": True},
    )

    assert set(x.name for x in tmp_path.iterdir()) == set(["glean_metrics.rs"])

    # Make sure descriptions made it in
    with (tmp_path / "glean_metrics.rs").open("r", encoding="utf-8") as fd:
        content = fd.read()

        assert "This is a custom ping" in content
        assert (
            "custom_ping: ::glean::private::__export::Lazy<::glean::private::"
            + "PingType> =\n    ::glean::private::__export::Lazy::new"
            in content
        )
        assert (
            "custom_ping_might_be_empty: ::glean::private::__export::Lazy<"
            + "::glean::private::PingType> =\n    ::glean::private::__export::Lazy::new"
            in content
        )

    # TODO we need a cargo.toml to run `cargo fmt` and `cargo clippy`
    # and I'm not quite sure how to do that in a non-Rust project for
    # the purpose of testing
    run_linters(tmp_path.glob("*.rs"))


def test_rust_generator():
    kdf = rust.rust_datatypes_filter

    # The Rust datatypes filter encodes strings using JSON-escaping
    assert kdf("\n") == '"\\n".into()'
    assert kdf([42, "\n"]) == 'vec![42, "\\n".into()]'
    assert kdf(metrics.Lifetime.ping) == "Lifetime::Ping"


def test_metric_type_name():
    event = metrics.Event(
        type="event",
        category="category",
        name="metric",
        bugs=["42"],
        notification_emails=["nobody@example.com"],
        description="description...",
        expires="never",
        extra_keys={"my_extra": {"description": "an extra", "type": "string"}},
    )

    assert rust.type_name(event) == "EventMetric<MetricExtra>"

    event = metrics.Event(
        type="event",
        category="category",
        name="metric",
        bugs=["42"],
        notification_emails=["nobody@example.com"],
        description="description...",
        expires="never",
    )

    assert rust.type_name(event) == "EventMetric<NoExtraKeys>"

    boolean = metrics.Boolean(
        type="boolean",
        category="category",
        name="metric",
        bugs=["http://bugzilla.mozilla.com/12345"],
        notification_emails=["nobody@example.com"],
        description="description...",
        expires="never",
    )
    assert rust.type_name(boolean) == "BooleanMetric"

    ping = pings.Ping(
        name="custom",
        description="description...",
        include_client_id=True,
        bugs=["http://bugzilla.mozilla.com/12345"],
        notification_emails=["nobody@nowhere.com"],
    )
    assert rust.type_name(ping) == "Ping<NoReasonCodes>"

    ping = pings.Ping(
        name="custom",
        description="description...",
        include_client_id=True,
        bugs=["http://bugzilla.mozilla.com/12345"],
        notification_emails=["nobody@nowhere.com"],
        reasons={"foo": "foolicious", "bar": "barlicious"},
    )
    assert rust.type_name(ping) == "Ping<CustomReasonCodes>"


def test_order_of_fields(tmp_path):
    """Test that translating metrics to Rust files keeps a stable order of fields."""
    translate.translate(
        ROOT / "data" / "core.yaml", "rust", tmp_path, {}, {"allow_reserved": True}
    )

    # Make sure descriptions made it in
    fd = (tmp_path / "glean_metrics.rs").open("r", encoding="utf-8")
    content = fd.read()
    fd.close()

    lines = content.splitlines()
    first_metric_fields = []
    found_metric = False

    # Get the fields of the first metric
    # Checking only one metric should be good enough for now
    for line in lines:
        if found_metric:
            if re.search("..Default::default()$", line):
                break

            # Collect only the fields
            field = line.strip().split(":")[0]
            first_metric_fields.append(field)
        elif re.search("CommonMetricData {", line):
            found_metric = True

    expected_fields = ["category", "name", "send_in_pings", "lifetime", "disabled"]

    # We only check the limited list of always available fields.
    size = len(expected_fields)
    assert expected_fields == first_metric_fields[:size]


def test_event_extra_keys_in_correct_order(tmp_path):
    """
    Assert that the extra keys appear in the parameter and the enumeration in
    the same order.

    https://bugzilla.mozilla.org/show_bug.cgi?id=1648768
    """
    translate.translate(
        ROOT / "data" / "events_with_types.yaml",
        "rust",
        tmp_path,
        {"namespace": "Foo"},
    )

    assert set(x.name for x in tmp_path.iterdir()) == set(["glean_metrics.rs"])

    with (tmp_path / "glean_metrics.rs").open("r", encoding="utf-8") as fd:
        content = fd.read()
        content = " ".join(content.split())
        assert (
            "pub struct PreferenceToggledExtra { "
            "pub enabled: Option<bool>, pub preference: "
            "Option<String>, pub swapped: Option<u32>, }" in content
        )
        assert (
            "const ALLOWED_KEYS: &'static [&'static str] = "
            '&["enabled", "preference", "swapped"];' in content
        )


def test_event_extra_keys_with_types(tmp_path):
    """
    Assert that the extra keys with types appear with their corresponding types.
    """
    translate.translate(
        ROOT / "data" / "events_with_types.yaml",
        "rust",
        tmp_path,
        {"namespace": "Foo"},
    )

    assert set(x.name for x in tmp_path.iterdir()) == set(["glean_metrics.rs"])

    with (tmp_path / "glean_metrics.rs").open("r", encoding="utf-8") as fd:
        content = fd.read()
        content = " ".join(content.split())
        assert (
            "impl ExtraKeys for PreferenceToggledExtra { "
            "const ALLOWED_KEYS: &'static [&'static str] = "
            '&["enabled", "preference", "swapped"];' in content
        )
        assert (
            "const ALLOWED_KEYS: &'static [&'static str] = "
            '&["enabled", "preference", "swapped"];' in content
        )


def test_object_metric(tmp_path):
    """
    Assert that an object metric is created.
    """
    translate.translate(
        ROOT / "data" / "object.yaml",
        "rust",
        tmp_path,
        {"namespace": "Foo"},
    )

    assert set(x.name for x in tmp_path.iterdir()) == set(["glean_metrics.rs"])

    with (tmp_path / "glean_metrics.rs").open("r", encoding="utf-8") as fd:
        content = fd.read()
        content = " ".join(content.split())

        assert "ObjectMetric<ThreadsObject>" in content
        assert "pub struct ThreadsObjectItem { " in content
        assert "frames: ThreadsObjectItemFrames, }" in content
        assert (
            "pub type ThreadsObjectItemFrames = "
            "Vec<ThreadsObjectItemFramesItem>;" in content
        )

        assert "pub struct ThreadsObjectItemFramesItem { " in content
        assert "module_index: Option<i64>, " in content
        assert "ip: Option<String>, " in content
        assert "trust: Option<String>, " in content
        assert "}" in content