Files
capa/tests/test_rule_cache.py
dependabot[bot] 7b23834d8e build(deps-dev): bump black from 25.12.0 to 26.3.0 (#2902)
* build(deps-dev): bump black from 25.12.0 to 26.3.0

Bumps [black](https://github.com/psf/black) from 25.12.0 to 26.3.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/25.12.0...26.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.3.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* style: auto-format with black and isort

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Capa Bot <capa-dev@mandiant.com>
2026-03-13 15:46:13 +01:00

155 lines
4.6 KiB
Python

# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import textwrap
import contextlib
from pathlib import Path
import capa.rules
import capa.helpers
import capa.rules.cache
R1 = capa.rules.Rule.from_yaml(textwrap.dedent("""
rule:
meta:
name: test rule
authors:
- user@domain.com
scopes:
static: function
dynamic: process
examples:
- foo1234
- bar5678
features:
- and:
- number: 1
- number: 2
"""))
R2 = capa.rules.Rule.from_yaml(textwrap.dedent("""
rule:
meta:
name: test rule 2
authors:
- user@domain.com
scopes:
static: function
dynamic: process
examples:
- foo1234
- bar5678
features:
- and:
- number: 3
- number: 4
"""))
def test_ruleset_cache_ids():
rs = capa.rules.RuleSet([R1])
content = capa.rules.cache.get_ruleset_content(rs)
rs2 = capa.rules.RuleSet([R1, R2])
content2 = capa.rules.cache.get_ruleset_content(rs2)
id = capa.rules.cache.compute_cache_identifier(content)
id2 = capa.rules.cache.compute_cache_identifier(content2)
assert id != id2
def test_ruleset_cache_save_load():
rs = capa.rules.RuleSet([R1])
content = capa.rules.cache.get_ruleset_content(rs)
id = capa.rules.cache.compute_cache_identifier(content)
assert id is not None
cache_dir = capa.rules.cache.get_default_cache_directory()
path = capa.rules.cache.get_cache_path(cache_dir, id)
with contextlib.suppress(OSError):
path.unlink()
capa.rules.cache.cache_ruleset(cache_dir, rs)
assert path.exists()
assert capa.rules.cache.load_cached_ruleset(cache_dir, content) is not None
def test_ruleset_cache_invalid():
rs = capa.rules.RuleSet([R1])
content = capa.rules.cache.get_ruleset_content(rs)
id = capa.rules.cache.compute_cache_identifier(content)
cache_dir = capa.rules.cache.get_default_cache_directory()
path = capa.rules.cache.get_cache_path(cache_dir, id)
with contextlib.suppress(OSError):
path.unlink()
capa.rules.cache.cache_ruleset(cache_dir, rs)
assert path.exists()
buf = path.read_bytes()
# corrupt the magic header
buf = b"x" + buf[1:]
# write the modified contents back to the file
path.write_bytes(buf)
# check if the file still exists
assert path.exists()
assert capa.rules.cache.load_cached_ruleset(cache_dir, content) is None
# the invalid cache should be deleted
assert not path.exists()
def test_rule_cache_dev_environment():
# generate rules cache
rs = capa.rules.RuleSet([R2])
content = capa.rules.cache.get_ruleset_content(rs)
id = capa.rules.cache.compute_cache_identifier(content)
cache_dir = capa.rules.cache.get_default_cache_directory()
cache_path = capa.rules.cache.get_cache_path(cache_dir, id)
# clear existing cache files
for f in cache_dir.glob("*.cache"):
f.unlink()
capa.rules.cache.cache_ruleset(cache_dir, rs)
assert cache_path.exists()
assert capa.helpers.is_cache_newer_than_rule_code(cache_dir) is True
capa_root = Path(__file__).resolve().parent.parent
cachepy = capa_root / "capa" / "rules" / "cache.py" # alternative: capa_root / "capa" / "rules" / "__init__.py"
# set cache's last modified time prior to code file's modified time
os.utime(cache_path, (cache_path.stat().st_atime, cachepy.stat().st_mtime - 600000))
# debug
def ts_to_str(ts):
from datetime import datetime
return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
for g in ((capa_root / "capa" / "rules").glob("*.py"), cache_dir.glob("*.cache")):
for p in g:
print(p, "\t", ts_to_str(p.stat().st_mtime)) # noqa: T201
assert capa.helpers.is_dev_environment() is True
assert capa.helpers.is_cache_newer_than_rule_code(cache_dir) is False