diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 006a9720e7..5b5a932a71 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,6 +31,9 @@ Since last release resources (materials/products) (#1675). Can pop resources as packaged from resource buffer, pushing resource onto a buffer defaults to stripping packaging (#1683) * CI support for Rocky Linux (#1691) * Added support for a ResBuf to behave as a single bulk storage with mixing & extraction of resources (#1687) +* Removed deprecated `smbchk.py` ABI consistency checking functionality and tests (#1706). This functionality was + originally deprecated in #1396, and is removed in #1706 to clean up deprecated functionality + with the pending v1.6 release. **Changed:** diff --git a/release/smbchk.py b/release/smbchk.py deleted file mode 100755 index fa6f0770c1..0000000000 --- a/release/smbchk.py +++ /dev/null @@ -1,219 +0,0 @@ -#! /usr/bin/env python -"""Collects & diffs public symobls in libcyclus.so. Used to ensure stability -between versions. Now with 100% fewer vowels! - -WARNING: this file is now deprecated. It is provided here for future use and -refactoring. - -The following tasks may be useful: - - # update the database to the most recent release tag - $ ./smbchk.py --update -t 1.X.Y - - # check that the existing database is stable - $ ./smbchk.py --check - - # check if HEAD is stable, a diff will be printed if it is not - $ ./smbchk.py --update -t HEAD --no-save --check - -""" -from __future__ import print_function, unicode_literals -import os -import io -import re -import sys -import argparse -import difflib -import subprocess -import pprint -try: - import simplejson as json -except ImportError: - import json - -NAME_RE = re.compile('([A-Za-z0-9~_:]+)') - -"""A blacklist for private API that gets caught by `nm`. Add additional -functions as needed. -""" -API_BLACKLIST = { - 'cyclus::SimInit::LoadResource', - 'cyclus::SimInit::LoadMaterial', - 'cyclus::SimInit::LoadProduct', - 'cyclus::SimInit::LoadComposition', - 'cyclus::TradeExecutor::ExecuteTrades', - 'cyclus::TradeExecutor::ExecuteTrades', - 'cyclus::Arc cyclus::TranslateArc', - 'cyclus::Arc cyclus::TranslateArc', - 'cyclus::Composition::NewDecay', -} - -SYMBOLS_REPLACEMENTS = [ - ("std::__cxx11::basic_string, std::allocator >", "std::string"), - ("std::__cxx11::list", "std::list"), - ("std::__cxx11::basic_stringstream", "std::basic_stringstream"), - ("[abi:cxx11]", ""), - ("string >", "string>") # fix string replacement when a '>' follows the string -] - - -def load(ns): - """Loads a database of symbols or returns an empty list.""" - if not os.path.isfile(ns.filename): - return [] - with io.open(ns.filename, 'rt') as f: - db = json.load(f) - return db - - -def save(db, ns): - """Saves a database of symbols.""" - with io.open(ns.filename, 'wb') as f: - json.dump(db, f, indent=1, separators=(',', ': ')) - - -def nm(ns): - """Obtains the latest symbols as a sorted list by running and parsing the - posix nm utility. Note that this is *not* compatible with Darwin's nm - utility because Apple is unfathomably 'special.' - """ - # in the nm command, the following option are for: - # -g: external only, ie this prints only public symbols - # -p: no-sort, doesn't sort the output first, since we will be sorting - # later this makes things a tad faster - # -C: demangles the C++ symbols. - # -fbsd: formats output in default 'bsd' style - # note that we are only going to pick up symbols in the cyclus namespace - # because everything else should be external to the cares of cyclus - # stability - lib = os.path.abspath(os.path.join(ns.prefix, 'lib', 'libcyclus.so')) - stdout = subprocess.check_output(['nm', '-g', '-p', '-C', '-fbsd', lib], - universal_newlines=True) - names = set() - ok_types = {'B', 'b', 'D', 'd', 'R', 'r', - 'S', 's', 'T', 't', 'W', 'w', 'u'} - for line in stdout.splitlines(): - # replace c++11 symbol by standard one - for symbol in SYMBOLS_REPLACEMENTS: - line = line.replace(symbol[0], symbol[1]) - - line = line.strip() - if len(line) == 0 or not line[0].isdigit(): - continue - val, typ, name = line.split(None, 2) - if not name.startswith('cyclus::'): - continue - if typ not in ok_types: - continue - if ' ' in name: - # handle funny private pointer cases - pre, post = name.split(' ', 1) - if pre.endswith('*') or post.startswith('std::__') or post.startswith('const* std::__'): - continue - # use trailing underscore naming convention to skip private variables - m = NAME_RE.match(name) - if m is None or m.group(1).endswith('_'): - continue - names.add(name) - return sorted(names) - - -def git_log(): - """Returns git SHA, date, and timestamp from log.""" - stdout = subprocess.check_output( - ['git', 'log', '--pretty=format:%H/%ci/%ct', '-n1']) - return stdout.decode().strip().split('/') - - -def core_version(): - stdout = subprocess.check_output(['cyclus', '--version'], - universal_newlines=True) - return stdout.splitlines()[0].strip() - - -def update(db, ns): - """Updates a symbol database with the latest values.""" - if ns.tag is None: - sys.exit("a tag for the version must be given when updating, eg '--tag 1.1'") - symbols = nm(ns) - sha, d, t = git_log() - entry = {'symbols': symbols, 'sha': sha, 'date': d, 'timestamp': t, - 'tag': ns.tag, 'version': core_version(), } - db.append(entry) - - -def diff(db, i, j): - """Diffs two database indices, returns string unified diff.""" - x = db[i] - y = db[j] - xsym = [_ for _ in x['symbols'] if _.split('(')[0] not in API_BLACKLIST] - ysym = [_ for _ in y['symbols'] if _.split('(')[0] not in API_BLACKLIST] - lines = difflib.unified_diff(xsym, ysym, - fromfile=x['version'], tofile=y['version'], - fromfiledate=x['date'], tofiledate=y['date']) - return '\n'.join(map(lambda x: x[:-1] if x.endswith('\n') else x, lines)) - - -def check(db): - """Checks if an API is stable, returns bool, prints debug info.""" - if len(db) < 2: - sys.exit('too few entries in database to check for stability') - stable = True - for i, (x, y) in enumerate(zip(db[:-1], db[1:])): - x = set(_ for _ in x['symbols'] if _.split( - '(')[0] not in API_BLACKLIST) - y = set(_ for _ in y['symbols'] if _.split( - '(')[0] not in API_BLACKLIST) - if not (frozenset(x) <= frozenset(y)): - stable = False - d = diff(db, i, i + 1) - print(d) - if stable: - print('ABI stability has been achieved!') - return stable - - -def main(args=None): - raise RuntimeError("Symbol checking has been deprecated!") - if os.name != 'posix': - sys.exit("must be run on a posix system, " - "'nm' utility not compatible elsewhere.") - p = argparse.ArgumentParser('smbchk', description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter,) - p.add_argument('--prefix', dest='prefix', default='../build', - help="location of lib dir with libcyclus, default '../build'") - p.add_argument('-f', '--filename', dest='filename', default='symbols.json', - help="historical symbols database, default 'symbols.json'") - p.add_argument('--dump', action='store_true', default=False, dest='dump', - help='dumps existing symbols') - p.add_argument('--update', dest='update', action='store_true', default=False, - help='updates the symbols with the current version') - p.add_argument('--save', action='store_true', default=True, dest='save', - help='saves the database') - p.add_argument('--no-save', action='store_false', default=True, dest='save', - help='does not save the database') - p.add_argument('-t', '--tag', dest='tag', default=None, - help='version tag used when updating, eg 1.0.0-rc5') - p.add_argument('-c', '--check', action='store_true', dest='check', default=False, - help='checks that the API is stable') - p.add_argument('-d', '--diff', nargs=2, dest='diff', type=int, default=(), - help='takes the difference between two database indices') - ns = p.parse_args(args=args) - - db = load(ns) - is_stable = None - if ns.update: - update(db, ns) - if ns.save: - save(db, ns) - if ns.dump: - pprint.pprint(db) - if ns.check: - is_stable = check(db) - if len(ns.diff) == 2: - d = diff(db, ns.diff[0], ns.diff[1]) - print(d) - return is_stable - -if __name__ == '__main__': - main() diff --git a/tests/test_abi.py b/tests/test_abi.py deleted file mode 100644 index e772ef933c..0000000000 --- a/tests/test_abi.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -import sys -import subprocess -import pytest - - -cycdir = os.path.dirname(os.path.dirname(__file__)) -reldir = os.path.join(cycdir, 'release') -sys.path.insert(0, reldir) - -import tools - -try: - import smbchk -except ImportError: - smbchk = None - - -def test_abi_stability(): - pytest.skip('manually remove this skip to test ABI stability') - if smbchk is None: - pytest.skip('Could not import smbchk!') - if os.name != 'posix': - pytest.skip('can only check for ABI stability on posix systems.') - libcyc = os.path.join(cycdir, 'build', 'lib', 'libcyclus.so') - if not os.path.exists(libcyc): - pytest.skip('libcyclus could not be found, ' - 'cannot check for ABI stability') - args = '--update -t HEAD --no-save --check'.split() - with tools.indir(reldir): - obs = smbchk.main(args=args) - assert(obs) - - diff --git a/tests/test_smbchk.py b/tests/test_smbchk.py deleted file mode 100644 index 5ac9301528..0000000000 --- a/tests/test_smbchk.py +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import print_function, unicode_literals -import os -import platform -import sys -from argparse import Namespace -import pytest - -from tools import skip_then_continue - -cycdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -reldir = os.path.join(cycdir, 'release') -blddir = os.path.join(cycdir, 'build') -sys.path.insert(0, reldir) - -try: - import smbchk -except ImportError: - smbchk = False - -@pytest.mark.skip(reason="symbol test has been deprecated") -def test_load(): - if not smbchk: - return - ns = Namespace(filename=os.path.join(reldir, 'symbols.json')) - db = smbchk.load(ns) - assert(isinstance(db, list)) - -@pytest.mark.skip(reason="symbol test has been deprecated") -def test_nm(): - if platform.system() == 'Darwin': - skip_then_continue("Skipping for Mac") - if not smbchk: - return - if os.name != 'posix' or not os.path.isdir(blddir): - return - ns = Namespace(prefix=blddir) - syms = smbchk.nm(ns) - assert ("cyclus::Agent::Agent(cyclus::Context*)" in syms) - -@pytest.mark.skip(reason="symbol test has been deprecated") -def test_diff(): - if not smbchk: - return - db = [{'symbols': ["cyclus::Agent::Agent(cyclus::Context*)"], - 'version': 'X', 'date': 'x.x.x'}, - {'symbols': ["cyclus::Agent::Agent(cyclus::Context*)", - "cyclus::Agent::~Agent()"], - 'version': 'Y', 'date': 'y.y.y'},] - obs = smbchk.diff(db, 0, 1) - assert(len(obs) > 0) - -@pytest.mark.skip(reason="symbol test has been deprecated") -def test_check(): - if not smbchk: - return - # adds to API - db = [{'symbols': ["cyclus::Agent::Agent(cyclus::Context*)"], - 'version': 'X', 'date': 'x.x.x'}, - {'symbols': ["cyclus::Agent::Agent(cyclus::Context*)", - "cyclus::Agent::~Agent()"], - 'version': 'Y', 'date': 'y.y.y'},] - obs = smbchk.check(db) - assert(obs) - - # removes from API - db.append({'symbols': ["cyclus::Agent::~Agent()"], - 'version': 'Z', 'date': 'z.z.z'}) - obs = smbchk.check(db) - assert not(obs) -