Commit 3dc84b26 authored by Kirill Smelkov's avatar Kirill Smelkov

Add basic tests

We will be adding more changes to improve nxdbom. Going without
testsuite is not practical because there will be no easy way to catch
regressions.
parent 660925dc
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2022 Nexedi SA and Contributors. # Copyright (C) 2022-2023 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com> # Started by Kirill Smelkov <kirr@nexedi.com>
# #
# This program is free software: you can Use, Study, Modify and Redistribute # This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your # it under the terms of the GNU General Public License version 3, or (at your
...@@ -177,11 +177,9 @@ def bom_node(XXX): ...@@ -177,11 +177,9 @@ def bom_node(XXX):
# namever extracts item name and version from an url/path. # namever extracts item name and version from an url/path.
# examples: # for example:
# #
# http://www.python.org/ftp/python/2.7.18/Python-2.7.18.tar.xz -> ('Python', '2.7.18') # http://www.python.org/ftp/python/2.7.18/Python-2.7.18.tar.xz -> ('Python', '2.7.18')
# https://git.savannah.gnu.org/gitweb/?p=config.git;a=snapshot;h=5e531d39;sf=tgz -> ('config', '5e531d39')
# https://github.com/nghttp2/nghttp2/archive/v1.40.0.tar.gz -> ('nghttp2', '1.40.0')
_gitweb_re = re.compile(r'/gitweb/\?p=(?P<name>\w+)\.git;a=snapshot;h=(?P<rev>\w+)') _gitweb_re = re.compile(r'/gitweb/\?p=(?P<name>\w+)\.git;a=snapshot;h=(?P<rev>\w+)')
_github_re = re.compile(r'github.com/\w+/(?P<name>\w+)/archive/(?P<rev>.+)$') _github_re = re.compile(r'github.com/\w+/(?P<name>\w+)/archive/(?P<rev>.+)$')
def namever(url): # -> (name, ver) def namever(url): # -> (name, ver)
...@@ -236,6 +234,28 @@ def removesuffix(s, suffix): ...@@ -236,6 +234,28 @@ def removesuffix(s, suffix):
# ---------------------------------------- # ----------------------------------------
# fmt_bom formats BOM into text.
def fmt_bom(bom): # -> str
outv = []
def emit(text):
outv.append(text+'\n')
# emit BOM grouped by kind
kinds = set()
for info in bom.values():
kinds.add(info.kind)
for kind in sorted(kinds):
if kind != '':
emit('\n>>> %ss:' % kind)
for bkey in sorted(bom):
info = bom[bkey]
if info.kind == kind:
# TODO autoalign
emit('%-28s %-10s %s' % (info.name, info.version, info.url))
return ''.join(outv)
def main(): def main():
if len(sys.argv) != 3 or sys.argv[1] not in ('software', 'node'): if len(sys.argv) != 3 or sys.argv[1] not in ('software', 'node'):
print(__doc__, file=sys.stderr) print(__doc__, file=sys.stderr)
...@@ -247,21 +267,10 @@ def main(): ...@@ -247,21 +267,10 @@ def main():
elif what == 'node': elif what == 'node':
bom = bom_node(arg) bom = bom_node(arg)
# print retrieved BOM grouped by kind # print retrieved BOM
# TODO also consider emitting machine-readable format, e.g. json if run with --json # TODO also consider emitting machine-readable format, e.g. json if run with --json
# TODO for json, consider schema from https://cyclonedx.org/specification/overview/ # TODO for json, consider schema from https://cyclonedx.org/specification/overview/
kinds = set() print(fmt_bom(bom))
for info in bom.values():
kinds.add(info.kind)
for kind in sorted(kinds):
if kind != '':
print('\n>>> %ss:' % kind)
for name in sorted(bom):
info = bom[name]
if info.kind == kind:
# TODO autoalign
print('%-28s %-10s %s' % (name, info.version, info.url))
if __name__ == '__main__': if __name__ == '__main__':
......
# Copyright (C) 2023 Nexedi SA and Contributors.
# Started by Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
import nxdbom
import pytest
import os
from os.path import dirname
@pytest.mark.parametrize('url,nameok,verok', [
('http://www.python.org/ftp/python/2.7.18/Python-2.7.18.tar.xz', 'Python', '2.7.18'),
('https://github.com/nghttp2/nghttp2/archive/v1.40.0.tar.gz', 'nghttp2', '1.40.0'),
('https://git.savannah.gnu.org/gitweb/?p=config.git;a=snapshot;h=5e531d39;sf=tgz', 'config', '5e531d39'),
])
def test_namever(url, nameok, verok):
assert nxdbom.namever(url) == (nameok, verok)
@pytest.mark.parametrize('url', [
'xxx.conf.in',
'xxx.cfg',
'xxx.cfg.in',
'xxx.cfg.jinja2.in',
'xxx.jinja2.cfg',
'xxx.asn',
'xxx/ltelogs.jinja2.sh',
'xxx/templates/wrapper.in',
'xxx/logrotate_entry.in',
'xxx/promise/yyy',
])
def test_isconf(url):
assert nxdbom.isconf(url) == True
# ---- BOM software ----
testv = [] # of (build, bomok)
def case1(build, bomok):
testv.append((build, bomok))
case1("""\
[ncurses]
recipe = slapos.recipe.cmmi
url = http://ftp.gnu.org/gnu/ncurses/ncurses-6.2.tar.gz
""", """\
ncurses 6.2 http://ftp.gnu.org/gnu/ncurses/ncurses-6.2.tar.gz
""")
for x in ('gcc', 'python'):
case1("""\
[%s]
recipe = slapos.recipe.build
""" % x, '') # empty
case1("""\
[template-logrotate-base]
recipe = slapos.recipe.template:jinja2
url = /srv/slapgrid/slappart47/srv/project/slapos/stack/logrotate/instance-logrotate-base.cfg.in
""", '') # config ignored
case1("""\
[python-mysqlclient]
recipe = zc.recipe.egg:custom
__buildout_installed__ = /ROOT/develop-eggs/mysqlclient-1.3.12-py2.7-linux-x86_64.egg
""", """
>>> eggs:
mysqlclient 1.3.12 https://pypi.org/project/mysqlclient/1.3.12/
""")
case1("""\
[slapos-toolbox-dependencies]
recipe = zc.recipe.egg
_d = /ROOT/develop-eggs
_e = /ROOT/eggs
eggs = lxml
pycurl
Mako
-- /ROOT/develop-eggs/lxml-4.9.1-py2.7-linux-x86_64.egg/x --
-- /ROOT/develop-eggs/pycurl-7.43.0-py2.7-linux-x86_64.egg/x --
-- /ROOT/eggs/Mako-1.1.4-py2.7.egg/x --
""", """
>>> eggs:
Mako 1.1.4 https://pypi.org/project/Mako/1.1.4/
lxml 4.9.1 https://pypi.org/project/lxml/4.9.1/
pycurl 7.43.0 https://pypi.org/project/pycurl/7.43.0/
""")
@pytest.mark.parametrize('build,bomok', testv)
def test_bom_software(tmpdir, build, bomok):
tmpdir = str(tmpdir)
build = '-- /ROOT/.installed.cfg --\n' + build
build = build.replace('/ROOT', tmpdir)
build = build.replace('/BASE', tmpdir+'/base')
ar = txtar_parse(build)
assert ar.comment == ''
for f, data in ar.files.items():
assert f.startswith(tmpdir)
os.makedirs(dirname(f), exist_ok=True)
with open(f, 'w') as _:
_.write(data)
bom = nxdbom.bom_software(tmpdir)
assert nxdbom.fmt_bom(bom) == bomok
# ---- txtar ----
# txtar_* provide support for archives in txtar format
# https://pkg.go.dev/golang.org/x/tools/txtar#hdr-Txtar_format
class txtar_Archive:
# .comment str
# .files {} name -> text
pass
def txtar_parse(text): # -> txtar_Archive
comment = ''
files = {}
current_file = None # current file | None for comments
current_text = '' # accumulator for text of current file
def flush(next_file):
nonlocal comment, current_file, current_text
if current_file is None:
comment = current_text
else:
files[current_file] = current_text
current_text = ''
current_file = next_file
for l in text.splitlines(keepends=True):
if not l.endswith('\n'):
l += '\n' # missing trailing newline on the final line
if l.startswith('-- ') and l.rstrip().endswith(' --'):
_ = l.rstrip()
_ = _.removeprefix('-- ')
_ = _.removesuffix(' --')
next_file = _.strip()
flush(next_file)
continue
current_text += l
flush(None)
ar = txtar_Archive()
ar.comment = comment
ar.files = files
return ar
...@@ -13,6 +13,9 @@ setup( ...@@ -13,6 +13,9 @@ setup(
keywords = 'Nexedi software build BOM', keywords = 'Nexedi software build BOM',
packages = find_packages(), packages = find_packages(),
extras_require = {
'test': ['pytest'],
},
entry_points= {'console_scripts': ['nxdbom = nxdbom:main']}, entry_points= {'console_scripts': ['nxdbom = nxdbom:main']},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment