snapshot of https://github.com/ActivityWatch/aw-core.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build / Test on ubuntu-latest, py-3.8 (push) Successful in 4m21s
Lint / lint (push) Successful in 25s
Build / Test on macOS-latest, py-3.8 (push) Has been cancelled
Build / Test on windows-latest, py-3.8 (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1s
Some checks failed
Build / Test on ubuntu-latest, py-3.8 (push) Successful in 4m21s
Lint / lint (push) Successful in 25s
Build / Test on macOS-latest, py-3.8 (push) Has been cancelled
Build / Test on windows-latest, py-3.8 (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1s
This commit is contained in:
715
tests/test_query2.py
Normal file
715
tests/test_query2.py
Normal file
@@ -0,0 +1,715 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Dict
|
||||
|
||||
import iso8601
|
||||
import pytest
|
||||
from aw_core.models import Event
|
||||
from aw_datastore.storages import MemoryStorage
|
||||
from aw_query import query
|
||||
from aw_query.exceptions import (
|
||||
QueryFunctionException,
|
||||
QueryInterpretException,
|
||||
QueryParseException,
|
||||
)
|
||||
from aw_query.query2 import (
|
||||
QDict,
|
||||
QFunction,
|
||||
QInteger,
|
||||
QList,
|
||||
QString,
|
||||
QVariable,
|
||||
_parse_token,
|
||||
_split_query_statements,
|
||||
)
|
||||
|
||||
from .utils import param_datastore_objects
|
||||
|
||||
|
||||
class MockDatastore(MemoryStorage):
|
||||
pass
|
||||
|
||||
|
||||
mock_ds = MockDatastore(testing=True)
|
||||
|
||||
|
||||
def test_query2_test_token_parsing():
|
||||
ns: Dict[str, Any] = {}
|
||||
(t, token), trash = _parse_token("123", ns)
|
||||
assert token == "123"
|
||||
assert t == QInteger
|
||||
(t, token), trash = _parse_token('"test"', ns)
|
||||
assert token == '"test"'
|
||||
assert t == QString
|
||||
(t, token), trash = _parse_token("'test'", ns)
|
||||
assert token == "'test'"
|
||||
assert t == QString
|
||||
(t, token), trash = _parse_token("'te\\'st'", ns)
|
||||
assert token == "'te\\'st'"
|
||||
assert t == QString
|
||||
(t, token), trash = _parse_token('"te\\"st"', ns)
|
||||
assert token == '"te\\"st"'
|
||||
assert t == QString
|
||||
(t, token), trash = _parse_token("test0xDEADBEEF", ns)
|
||||
assert token == "test0xDEADBEEF"
|
||||
assert t == QVariable
|
||||
(t, token), trash = _parse_token("test1337(')')", ns)
|
||||
assert token == "test1337(')')"
|
||||
assert t == QFunction
|
||||
(t, token), trash = _parse_token("test1337('test\\'test',\"test\\\"test\")", ns)
|
||||
assert token == "test1337('test\\'test',\"test\\\"test\")"
|
||||
assert t == QFunction
|
||||
(t, token), trash = _parse_token("[1, 'a', {}]", ns)
|
||||
assert token == "[1, 'a', {}]"
|
||||
assert t == QList
|
||||
(t, token), trash = _parse_token("{'a': 1, 'b}': 2}", ns)
|
||||
assert token == "{'a': 1, 'b}': 2}"
|
||||
assert t == QDict
|
||||
|
||||
assert _parse_token("", ns) == ((None, ""), "")
|
||||
|
||||
with pytest.raises(QueryParseException):
|
||||
_parse_token(None, ns) # type: ignore
|
||||
|
||||
with pytest.raises(QueryParseException):
|
||||
_parse_token('"', ns)
|
||||
|
||||
with pytest.raises(QueryParseException):
|
||||
_parse_token("#", ns)
|
||||
|
||||
|
||||
def test_dict():
|
||||
ds = mock_ds
|
||||
ns: Dict[str, Any] = {}
|
||||
d_str = "{'a': {'a': {'a': 1}}, 'b': {'b\\'\"': ':'}}"
|
||||
d = QDict.parse(d_str, ns)
|
||||
expected_res = {"a": {"a": {"a": 1}}, "b": {"b'\"": ":"}}
|
||||
assert expected_res == d.interpret(ds, ns)
|
||||
|
||||
# Key in dict is not a string
|
||||
with pytest.raises(QueryParseException):
|
||||
d_str = "{b: 1}"
|
||||
d = QDict.parse(d_str, ns)
|
||||
|
||||
# Key in dict without a value
|
||||
with pytest.raises(QueryParseException):
|
||||
d_str = "{'test': }"
|
||||
d = QDict.parse(d_str, ns)
|
||||
|
||||
# Char following key string is not a :
|
||||
with pytest.raises(QueryParseException):
|
||||
d_str = "{'test'p 1}"
|
||||
d = QDict.parse(d_str, ns)
|
||||
|
||||
with pytest.raises(QueryParseException):
|
||||
d_str = "{'test': #}"
|
||||
d = QDict.parse(d_str, ns)
|
||||
|
||||
# Semicolon without key
|
||||
with pytest.raises(QueryParseException):
|
||||
d_str = "{:}"
|
||||
d = QDict.parse(d_str, ns)
|
||||
|
||||
# Trailing comma
|
||||
with pytest.raises(QueryParseException):
|
||||
d_str = "{'test':1,}"
|
||||
d = QDict.parse(d_str, ns)
|
||||
|
||||
|
||||
def test_list():
|
||||
ds = mock_ds
|
||||
ns: Dict[str, Any] = {}
|
||||
l_str = "[1,2,[[3],4],5]"
|
||||
ls = QList.parse(l_str, ns)
|
||||
expected_res = [1, 2, [[3], 4], 5]
|
||||
assert expected_res == ls.interpret(ds, ns)
|
||||
|
||||
l_str = "['\\'',\"\\\"\"]"
|
||||
ls = QList.parse(l_str, ns)
|
||||
expected_res = ["'", '"']
|
||||
assert expected_res == ls.interpret(ds, ns)
|
||||
|
||||
l_str = "[]"
|
||||
ls = QList.parse(l_str, ns)
|
||||
expected_res = []
|
||||
assert expected_res == ls.interpret(ds, ns)
|
||||
|
||||
# Comma without pre/post value
|
||||
with pytest.raises(QueryParseException):
|
||||
l_str = "[,]"
|
||||
ls = QList.parse(l_str, ns)
|
||||
|
||||
# Comma without post value
|
||||
with pytest.raises(QueryParseException):
|
||||
l_str = "[1,]"
|
||||
ls = QList.parse(l_str, ns)
|
||||
|
||||
# Comma without pre value
|
||||
with pytest.raises(QueryParseException):
|
||||
l_str = "[,2]"
|
||||
ls = QList.parse(l_str, ns)
|
||||
|
||||
|
||||
def test_query2_bogus_query():
|
||||
ds = mock_ds
|
||||
qname = "test"
|
||||
qstartdate = datetime.now(tz=timezone.utc)
|
||||
qenddate = qstartdate
|
||||
|
||||
# Nothing to assign
|
||||
with pytest.raises(QueryParseException):
|
||||
example_query = "a="
|
||||
query(qname, example_query, qstartdate, qenddate, ds)
|
||||
|
||||
# Assign to non-variable
|
||||
with pytest.raises(QueryParseException):
|
||||
example_query = "1 = 2"
|
||||
query(qname, example_query, qstartdate, qenddate, ds)
|
||||
|
||||
# Unclosed function
|
||||
with pytest.raises(QueryParseException):
|
||||
example_query = "a = unclosed_function(var1"
|
||||
query(qname, example_query, qstartdate, qenddate, ds)
|
||||
|
||||
# Two tokens in assignment
|
||||
with pytest.raises(QueryParseException):
|
||||
example_query = "asd nop() = 2"
|
||||
query(qname, example_query, qstartdate, qenddate, ds)
|
||||
|
||||
# Unclosed string
|
||||
with pytest.raises(QueryParseException):
|
||||
example_query = 'asd = "something is wrong with me'
|
||||
query(qname, example_query, qstartdate, qenddate, ds)
|
||||
|
||||
# Two tokens in value
|
||||
with pytest.raises(QueryParseException):
|
||||
example_query = "asd = asd1 asd2"
|
||||
query(qname, example_query, qstartdate, qenddate, ds)
|
||||
|
||||
|
||||
def test_query2_query_function_calling():
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
|
||||
# Function which doesn't exist
|
||||
with pytest.raises(QueryInterpretException):
|
||||
example_query = "RETURN = asd();"
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
# Function which does exist with invalid arguments
|
||||
with pytest.raises(QueryInterpretException):
|
||||
example_query = "RETURN = nop(badarg);"
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
# Function which does exist with valid arguments
|
||||
example_query = "RETURN = nop();"
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
|
||||
def test_query2_return_value():
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
example_query = "RETURN = 1;"
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
assert result == 1
|
||||
|
||||
example_query = "RETURN = 'testing 123'"
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
assert result == "testing 123"
|
||||
|
||||
example_query = "RETURN = {'a': 1}"
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
assert result == {"a": 1}
|
||||
|
||||
# Nothing to return
|
||||
with pytest.raises(QueryParseException):
|
||||
example_query = "a=1"
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
|
||||
def test_query2_semicolon_in_string():
|
||||
"""Test that semicolons inside string literals don't break query parsing (issue #145)."""
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
|
||||
# Semicolon in double-quoted string
|
||||
example_query = 'RETURN = "hello;world"'
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
assert result == "hello;world"
|
||||
|
||||
# Semicolon in single-quoted string
|
||||
example_query = "RETURN = 'hello;world'"
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
assert result == "hello;world"
|
||||
|
||||
# Multiple statements where one value contains a semicolon in a string
|
||||
example_query = 'a = "foo;bar"; RETURN = a'
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
assert result == "foo;bar"
|
||||
|
||||
|
||||
def test_split_query_statements():
|
||||
"""Unit test for _split_query_statements."""
|
||||
# Basic split
|
||||
assert _split_query_statements("a=1;b=2") == ["a=1", "b=2"]
|
||||
# Semicolon inside double quotes should NOT split
|
||||
assert _split_query_statements('a="x;y";b=2') == ['a="x;y"', "b=2"]
|
||||
# Semicolon inside single quotes should NOT split
|
||||
assert _split_query_statements("a='x;y';b=2") == ["a='x;y'", "b=2"]
|
||||
# Trailing semicolon — empty trailing part is omitted
|
||||
assert _split_query_statements("a=1;") == ["a=1"]
|
||||
|
||||
|
||||
def test_query2_multiline():
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
example_query = """
|
||||
my_multiline_string = "a
|
||||
b";
|
||||
RETURN = my_multiline_string;
|
||||
"""
|
||||
result = query(qname, example_query, starttime, endtime, ds)
|
||||
assert result == "a\nb"
|
||||
|
||||
|
||||
def test_query2_function_invalid_types():
|
||||
"""Tests the q2_typecheck decorator"""
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
|
||||
# int instead of str
|
||||
example_query = """
|
||||
events = [];
|
||||
RETURN = filter_keyvals(events, 666, ["invalid_val"]);
|
||||
"""
|
||||
with pytest.raises(QueryFunctionException):
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
# str instead of list
|
||||
example_query = """
|
||||
events = [];
|
||||
RETURN = filter_keyvals(events, "2", "invalid_val");
|
||||
"""
|
||||
with pytest.raises(QueryFunctionException):
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
# FIXME: For unknown reasons, query2 drops the second argument
|
||||
# when the first argument is a bare []
|
||||
"""
|
||||
example_query = '''
|
||||
RETURN = filter_keyvals([], "2", "invalid_val");
|
||||
'''
|
||||
with pytest.raises(QueryFunctionException) as e:
|
||||
result = query(qname, example_query, starttime, endtime, None)
|
||||
"""
|
||||
|
||||
|
||||
def test_query2_function_invalid_argument_count():
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
example_query = "RETURN=nop(nop())"
|
||||
with pytest.raises(QueryInterpretException):
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
|
||||
def test_query2_merge_subwatcher_fields_invalid_conflict():
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
example_query = """
|
||||
base = [];
|
||||
sub = [];
|
||||
RETURN = merge_subwatcher_fields(base, sub, ["project"], "invalid");
|
||||
"""
|
||||
|
||||
with pytest.raises(QueryFunctionException, match="conflict must be"):
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
|
||||
def test_query2_categorize_invalid_priority():
|
||||
ds = mock_ds
|
||||
qname = "asd"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
example_query = """
|
||||
events = [];
|
||||
RETURN = categorize(events, [[["test"], {"regex": "test", "priority": "high"}]]);
|
||||
"""
|
||||
with pytest.raises(QueryFunctionException, match="integer"):
|
||||
query(qname, example_query, starttime, endtime, ds)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_function_in_function(datastore):
|
||||
qname = "asd"
|
||||
bid = "test_bucket"
|
||||
starttime = iso8601.parse_date("1970-01-01")
|
||||
endtime = iso8601.parse_date("1970-01-02")
|
||||
example_query = f"""
|
||||
RETURN=limit_events(query_bucket("{bid}"), 1);
|
||||
"""
|
||||
try:
|
||||
# Setup buckets
|
||||
bucket1 = datastore.create_bucket(
|
||||
bucket_id=bid, type="test", client="test", hostname="test", name="test"
|
||||
)
|
||||
# Prepare buckets
|
||||
e1 = Event(data={}, timestamp=starttime, duration=timedelta(seconds=1))
|
||||
bucket1.insert(e1)
|
||||
result = query(qname, example_query, starttime, endtime, datastore)
|
||||
assert 1 == len(result)
|
||||
finally:
|
||||
datastore.delete_bucket(bid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_query_functions(datastore):
|
||||
"""
|
||||
Just test calling all functions just to see something isn't completely broken
|
||||
In many cases the functions doesn't change the result at all, so it's not a test
|
||||
for testing the validity of the data the functions transform
|
||||
"""
|
||||
bid = "test_'bucket"
|
||||
qname = "test"
|
||||
starttime = iso8601.parse_date("1970")
|
||||
endtime = starttime + timedelta(hours=1)
|
||||
|
||||
example_query = """
|
||||
bid = "{bid}";
|
||||
events = query_bucket("{bid}");
|
||||
events2 = query_bucket('{bid_escaped}');
|
||||
events2 = filter_keyvals(events2, "label", ["test1"]);
|
||||
events2 = exclude_keyvals(events2, "label", ["test2"]);
|
||||
events = filter_period_intersect(events, events2);
|
||||
events = filter_keyvals_regex(events, "label", ".*");
|
||||
events = limit_events(events, 1);
|
||||
events = merge_events_by_keys(events, ["label"]);
|
||||
events = chunk_events_by_key(events, "label");
|
||||
events = split_url_events(events);
|
||||
events = sort_by_timestamp(events);
|
||||
events = sort_by_duration(events);
|
||||
events = categorize(events, [[["test", "subtest"], {{"regex": "test1"}}]]);
|
||||
duration = sum_durations(events);
|
||||
eventcount = query_bucket_eventcount(bid);
|
||||
asd = nop();
|
||||
RETURN = {{"events": events, "eventcount": eventcount}};
|
||||
""".format(bid=bid, bid_escaped=bid.replace("'", "\\'"))
|
||||
try:
|
||||
bucket = datastore.create_bucket(
|
||||
bucket_id=bid, type="test", client="test", hostname="test", name="asd"
|
||||
)
|
||||
bucket.insert(
|
||||
Event(
|
||||
data={"label": "test1"},
|
||||
timestamp=starttime,
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
)
|
||||
result = query(qname, example_query, starttime, endtime, datastore)
|
||||
assert result["eventcount"] == 1
|
||||
assert len(result["events"]) == 1
|
||||
assert result["events"][0].data["label"] == "test1"
|
||||
assert result["events"][0].data["$category"] == ["test", "subtest"]
|
||||
finally:
|
||||
datastore.delete_bucket(bid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_basic_query(datastore):
|
||||
name = "A label/name for a test bucket"
|
||||
bid1 = "bucket1"
|
||||
bid2 = "bucket2"
|
||||
qname = "test_query_basic"
|
||||
starttime = iso8601.parse_date("1970")
|
||||
endtime = starttime + timedelta(hours=1)
|
||||
|
||||
example_query = f"""
|
||||
bid1 = "{bid1}";
|
||||
bid2 = "{bid2}";
|
||||
events = query_bucket(bid1);
|
||||
intersect_events = query_bucket(bid2);
|
||||
RETURN = filter_period_intersect(events, intersect_events);
|
||||
"""
|
||||
|
||||
try:
|
||||
# Setup buckets
|
||||
bucket1 = datastore.create_bucket(
|
||||
bucket_id=bid1, type="test", client="test", hostname="test", name=name
|
||||
)
|
||||
bucket2 = datastore.create_bucket(
|
||||
bucket_id=bid2, type="test", client="test", hostname="test", name=name
|
||||
)
|
||||
# Prepare buckets
|
||||
e1 = Event(
|
||||
data={"label": "test1"}, timestamp=starttime, duration=timedelta(seconds=1)
|
||||
)
|
||||
e2 = Event(
|
||||
data={"label": "test2"},
|
||||
timestamp=starttime + timedelta(seconds=2),
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
et = Event(
|
||||
data={"label": "intersect-label"},
|
||||
timestamp=starttime,
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
bucket1.insert(e1)
|
||||
bucket1.insert(e2)
|
||||
bucket2.insert(et)
|
||||
# Query
|
||||
result = query(qname, example_query, starttime, endtime, datastore)
|
||||
# Assert
|
||||
assert len(result) == 1
|
||||
assert result[0]["data"]["label"] == "test1"
|
||||
finally:
|
||||
datastore.delete_bucket(bid1)
|
||||
datastore.delete_bucket(bid2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_test_merged_keys(datastore):
|
||||
name = "A label/name for a test bucket"
|
||||
bid = "bucket1"
|
||||
qname = "test_query_merged_keys"
|
||||
starttime = iso8601.parse_date("2080")
|
||||
endtime = starttime + timedelta(hours=1)
|
||||
|
||||
example_query = f"""
|
||||
bid1 = "{bid}";
|
||||
events = query_bucket(bid1);
|
||||
events = merge_events_by_keys(events, ["label1", "label2"]);
|
||||
events = sort_by_duration(events);
|
||||
eventcount = query_bucket_eventcount(bid1);
|
||||
RETURN = {{"events": events, "eventcount": eventcount}};
|
||||
"""
|
||||
try:
|
||||
# Setup buckets
|
||||
bucket1 = datastore.create_bucket(
|
||||
bucket_id=bid, type="test", client="test", hostname="test", name=name
|
||||
)
|
||||
# Prepare buckets
|
||||
e1 = Event(
|
||||
data={"label1": "test1", "label2": "test1"},
|
||||
timestamp=starttime,
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
e2 = Event(
|
||||
data={"label1": "test1", "label2": "test1"},
|
||||
timestamp=starttime + timedelta(seconds=1),
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
e3 = Event(
|
||||
data={"label1": "test1", "label2": "test2"},
|
||||
timestamp=starttime + timedelta(seconds=2),
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
bucket1.insert(e3)
|
||||
bucket1.insert(e1)
|
||||
bucket1.insert(e2)
|
||||
# Query
|
||||
result = query(qname, example_query, starttime, endtime, datastore)
|
||||
# Assert
|
||||
print(result)
|
||||
assert len(result["events"]) == 2
|
||||
assert result["eventcount"] == 3
|
||||
assert result["events"][0]["data"]["label1"] == "test1"
|
||||
assert result["events"][0]["data"]["label2"] == "test1"
|
||||
assert result["events"][0]["duration"] == timedelta(seconds=2)
|
||||
assert result["events"][1]["data"]["label1"] == "test1"
|
||||
assert result["events"][1]["data"]["label2"] == "test2"
|
||||
assert result["events"][1]["duration"] == timedelta(seconds=1)
|
||||
finally:
|
||||
datastore.delete_bucket(bid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_fancy_query(datastore):
|
||||
"""
|
||||
Tests:
|
||||
- find_bucket
|
||||
- simplify_window_titles
|
||||
"""
|
||||
name = "A label/name for a test bucket"
|
||||
bid1 = "bucket-the-one"
|
||||
qname = "test_query_basic_fancy"
|
||||
starttime = iso8601.parse_date("1970")
|
||||
endtime = starttime + timedelta(hours=1)
|
||||
|
||||
example_query = f"""
|
||||
bid = find_bucket("{bid1[:10]}");
|
||||
events = query_bucket(bid);
|
||||
RETURN = simplify_window_titles(events, "title");
|
||||
"""
|
||||
|
||||
try:
|
||||
# Setup buckets
|
||||
bucket_main = datastore.create_bucket(
|
||||
bucket_id=bid1, type="test", client="test", hostname="test", name=name
|
||||
)
|
||||
# Prepare buckets
|
||||
e1 = Event(
|
||||
data={"title": "(2) YouTube"},
|
||||
timestamp=starttime,
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
bucket_main.insert(e1)
|
||||
# Query
|
||||
result = query(qname, example_query, starttime, endtime, datastore)
|
||||
# Assert
|
||||
assert result[0]["data"]["title"] == "YouTube"
|
||||
finally:
|
||||
datastore.delete_bucket(bid1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_query_categorize(datastore):
|
||||
bid = "test_bucket"
|
||||
qname = "test"
|
||||
starttime = iso8601.parse_date("1970")
|
||||
endtime = starttime + timedelta(hours=1)
|
||||
|
||||
example_query = rf"""
|
||||
events = query_bucket("{bid}");
|
||||
events = sort_by_timestamp(events);
|
||||
events = categorize(events, [
|
||||
[["test"], {{"regex": "test"}}],
|
||||
[["test", "subtest"], {{"regex": "test\w"}}]
|
||||
]);
|
||||
events_by_cat = merge_events_by_keys(events, ["$category"]);
|
||||
RETURN = {{"events": events, "events_by_cat": events_by_cat}};
|
||||
"""
|
||||
try:
|
||||
bucket = datastore.create_bucket(
|
||||
bucket_id=bid, type="test", client="test", hostname="test", name="asd"
|
||||
)
|
||||
events = [
|
||||
Event(
|
||||
data={"label": "test"},
|
||||
timestamp=starttime,
|
||||
duration=timedelta(seconds=1),
|
||||
),
|
||||
Event(
|
||||
data={"label": "testwithmoredetail"},
|
||||
timestamp=starttime + timedelta(seconds=1),
|
||||
duration=timedelta(seconds=1),
|
||||
),
|
||||
Event(
|
||||
data={"label": "testwithmoredetail"},
|
||||
timestamp=starttime + timedelta(seconds=2),
|
||||
duration=timedelta(seconds=1),
|
||||
),
|
||||
]
|
||||
bucket.insert(events)
|
||||
result = query(qname, example_query, starttime, endtime, datastore)
|
||||
print(result)
|
||||
assert len(result["events"]) == 3
|
||||
assert result["events"][0].data["label"] == "test"
|
||||
assert result["events"][0].data["$category"] == ["test"]
|
||||
assert result["events"][1].data["$category"] == ["test", "subtest"]
|
||||
|
||||
assert len(result["events_by_cat"]) == 2
|
||||
assert result["events_by_cat"][0].data["$category"] == ["test"]
|
||||
assert result["events_by_cat"][1].data["$category"] == ["test", "subtest"]
|
||||
assert result["events_by_cat"][1].duration == timedelta(seconds=2)
|
||||
finally:
|
||||
datastore.delete_bucket(bid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_query_categorize_priority(datastore):
|
||||
bid = "test_bucket_priority"
|
||||
qname = "test"
|
||||
starttime = iso8601.parse_date("1970")
|
||||
endtime = starttime + timedelta(hours=1)
|
||||
|
||||
example_query = rf"""
|
||||
events = query_bucket("{bid}");
|
||||
events = categorize(events, [
|
||||
[["A"], {{"regex": "test", "priority": 25}}],
|
||||
[["B", "B1"], {{"regex": "test"}}]
|
||||
]);
|
||||
RETURN = events;
|
||||
"""
|
||||
try:
|
||||
bucket = datastore.create_bucket(
|
||||
bucket_id=bid, type="test", client="test", hostname="test", name="asd"
|
||||
)
|
||||
bucket.insert(
|
||||
Event(
|
||||
data={"label": "test"},
|
||||
timestamp=starttime,
|
||||
duration=timedelta(seconds=1),
|
||||
)
|
||||
)
|
||||
result = query(qname, example_query, starttime, endtime, datastore)
|
||||
assert len(result) == 1
|
||||
assert result[0].data["$category"] == ["A"]
|
||||
finally:
|
||||
datastore.delete_bucket(bid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("datastore", param_datastore_objects())
|
||||
def test_query2_flood_with_pulsetime(datastore):
|
||||
"""Test that flood() accepts an optional pulsetime argument (fixes WorkReport query bug)."""
|
||||
bid = "test_flood_bucket"
|
||||
qname = "test_flood"
|
||||
starttime = iso8601.parse_date("1970")
|
||||
endtime = starttime + timedelta(hours=1)
|
||||
|
||||
try:
|
||||
bucket = datastore.create_bucket(
|
||||
bucket_id=bid,
|
||||
type="test",
|
||||
client="test",
|
||||
hostname="test",
|
||||
name="flood_test",
|
||||
)
|
||||
# Two events with a 60-second gap — flood with pulsetime=300 should merge them
|
||||
bucket.insert(
|
||||
Event(
|
||||
data={"app": "Firefox"},
|
||||
timestamp=starttime,
|
||||
duration=timedelta(seconds=10),
|
||||
)
|
||||
)
|
||||
bucket.insert(
|
||||
Event(
|
||||
data={"app": "Firefox"},
|
||||
timestamp=starttime + timedelta(seconds=70),
|
||||
duration=timedelta(seconds=10),
|
||||
)
|
||||
)
|
||||
|
||||
# flood with default pulsetime (5s) — gap is 60s, events should NOT merge
|
||||
result_default = query(
|
||||
qname,
|
||||
f'events = query_bucket("{bid}"); events = flood(events); RETURN = events;',
|
||||
starttime,
|
||||
endtime,
|
||||
datastore,
|
||||
)
|
||||
assert len(result_default) == 2
|
||||
|
||||
# flood with pulsetime=300 — gap is 60s < 300s, events SHOULD merge
|
||||
result_pulsetime = query(
|
||||
qname,
|
||||
f'events = query_bucket("{bid}"); events = flood(events, 300); RETURN = events;',
|
||||
starttime,
|
||||
endtime,
|
||||
datastore,
|
||||
)
|
||||
assert len(result_pulsetime) == 1
|
||||
finally:
|
||||
datastore.delete_bucket(bid)
|
||||
Reference in New Issue
Block a user