nanobrush / tests /test_map_references.py
nmarshall's picture
research: map reference sources
9c9a58f
Raw History Blame Contribute Delete
5.26 kB
"""Static-index correctness tests independent of the original payload corpus."""
import hashlib
from pathlib import Path
import tempfile
import unittest
import map_references as mapper
class LexicalMappingTests(unittest.TestCase):
def test_comments_and_literals_do_not_become_dependencies(self):
source = '''// #include "fake1.ixpl"
/* C::loadConfigFile("bad.cfg"); */
string x = "C::fake(1)";
string raw = \\"[\n#include "fake2.ixpl"\nC::fake(2);\n]";
#include "Core.ixpl"
namespace C = Core;
void real() { C::loadConfigFile("real.cfg", cfg); }
'''
report = mapper.scan_ixpl(source)
self.assertEqual([(x['include'],x['line']) for x in report['includes']], [('Core.ixpl',8)])
self.assertEqual([(c['qualifier'],c['method']) for c in report['qualified_calls']], [('Core','loadConfigFile')])
def test_balanced_call_arguments_and_opaque_commas(self):
source = 'namespace C = Core;\nvoid f() { C::queueTask(id, int[]{1, 2}, nested(3, 4), "x,y"); }'
call = mapper.scan_ixpl(source)['qualified_calls'][0]
self.assertEqual(call['arguments'],['id','int[]{1, 2}','nested(3, 4)','"x,y"'])
def test_interrupt_overloads_keep_signatures_and_source_lines(self):
source = ('int INT_A = 7;\nvoid get(int &x);\n'
'void get(int &x) interrupt(+INT_A, x);\n'
'int get() {int x; interrupt(+INT_A, x); return x;}\n')
wrappers = mapper.scan_ixpl(source)['interrupt_wrappers']
self.assertEqual([w['wrapper']['signature'] for w in wrappers],['void get(int &x)','int get()'])
self.assertEqual([w['line'] for w in wrappers],[3,4])
self.assertEqual(mapper.constant_value(wrappers[0]['arguments'][0],{'INT_A':7}),7)
def test_historical_struct_return_is_not_attached_to_previous_wrapper(self):
source = ('type Brush = (int x);\nvoid deselect(int x) interrupt(+1,x);\n'
'Brush &read() {Brush b; interrupt(+2,b); return b;}\n')
wrappers=mapper.scan_ixpl(source)['interrupt_wrappers']
self.assertEqual(wrappers[1]['wrapper']['name'],'read')
self.assertEqual(wrappers[1]['wrapper']['line'],3)
self.assertEqual(wrappers[1]['line'],3)
def test_numeric_resolution_is_not_code_execution(self):
self.assertEqual(mapper.constant_value('_INT_BEGIN + 5',{'_INT_BEGIN':249}),254)
for value in ['__import__("os").system("bad")','X + 1','2 * 3']:
with self.subTest(value=value), self.assertRaises(ValueError):
mapper.constant_value(value,{})
def test_literal_names_resolve_only_unambiguous_bindings(self):
report = mapper.scan_ixpl('string prefix = "camera.";\nstring name = prefix+"toggle";')
self.assertEqual(mapper.literal_string('name',report['string_bindings']),'camera.toggle')
for value in ['missing','"bad"+','call()']:
self.assertIsNone(mapper.literal_string(value,report['string_bindings']))
self.assertIsNone(mapper.literal_string('prefix',{'prefix':[{'expression':'"a"'},{'expression':'"b"'}]}))
self.assertIsNone(mapper.literal_string('cycle',{'cycle':[{'expression':'cycle'}]}))
def test_large_multiline_literal_is_opaque(self):
source = 'string data = \\"[' + (' ' * 1000 + '\n') * 80 + ']";\nvoid f() { Core::undo(); }'
self.assertEqual(mapper.scan_ixpl(source)['qualified_calls'][0]['method'],'undo')
for source in ['/* unterminated','string s = "bad','string s = \\"[bad']:
with self.assertRaises(ValueError):mapper.lex(source)
def test_build_graph_reports_missing_and_ambiguous_candidates(self):
with tempfile.TemporaryDirectory() as temp:
root = Path(temp).resolve()
content = {'tools/includePaths.txt':b'..\\inc1\n..\\inc2\n',
'inc1/Core.ixpl':b'namespace Core {}', 'inc2/Core.ixpl':b'namespace Core {}',
'data/sources/plugins/tool.ixpl':b'#include "Core.ixpl"\n#include "Missing.ixpl"\n'}
inventory = {'build':'synthetic','archive_sha256':'a'*64,'files':[]}
for name,raw in content.items():
path=root/name;path.parent.mkdir(parents=True,exist_ok=True);path.write_bytes(raw)
inventory['files'].append({'path':name,'sha256':hashlib.sha256(raw).hexdigest()})
build = mapper.index_build(root,inventory)
plugin = next(f for f in build['files'] if f['category']=='plugin')
self.assertEqual([e['status'] for e in plugin['includes']],['ambiguous','missing'])
(root/'inc1/Core.ixpl').write_bytes(b'changed')
with self.assertRaises(ValueError):mapper.index_build(root,inventory)
def test_diffs_distinguish_renames_copies_and_changes(self):
old={'build':'a','files':[{'path':'old.txt','sha256':'1'},{'path':'retained.txt','sha256':'2'}]}
new={'build':'b','files':[{'path':'new.txt','sha256':'1'},{'path':'retained.txt','sha256':'2'}, {'path':'copy.txt','sha256':'2'}]}
diff=mapper.differences([old,new])[0]
self.assertEqual(diff['identical_rename_candidates'],[{'from':'old.txt','to':'new.txt'}])
self.assertEqual(diff['identical_copy_candidates'],[{'from':'retained.txt','to':'copy.txt'}])