Source code for tests.test_cases

# -*- coding: utf-8 -*-
# MIT License
#
# Copyright (c) 2017 IRISA, Jean Coquet, Pierre Vignet, Mateo Boudet
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Contributor(s): Jean Coquet, Pierre Vignet, Mateo Boudet
"""
This module is used to translate Biopax test cases to cadbiom models
and compare them with the cadbiom model reference (if it exists).

.. note:: To add a test, please add it to test_pool dictionnary.
    key: name of the test,
    value: tuple of a list of uris and path to a potential blacklist file.

"""
# Standard imports
from __future__ import unicode_literals
from __future__ import print_function
import os
from functools import partial
import pytest

# Cadbiom imports
from cadbiom.models.guard_transitions.translators.chart_xml import MakeModelFromXmlFile
from cadbiom.models.guard_transitions.analyser.ana_visitors import TableVisitor
from cadbiom.models.clause_constraints.mcl.MCLTranslators import GT2Clauses
from cadbiom.models.clause_constraints.CLDynSys import CLDynSys
from cadbiom.models.biosignal.translators.TestExprCompiler import Reporter
from cadbiom.models.biosignal.translators.gt_visitors import compile_cond, compile_event
from cadbiom.models.biosignal.sig_expr import SigDefaultExpr, SigWhenExpr
from cadbiom_cmd.models import graph_isomorph_test

# Custom imports
import biopax2cadbiom.biopax_converter as b2c
from biopax2cadbiom.commons import DIR_TEST_CASES, DIR_LOGS, SPARQL_PATH, SPARQL_LIMIT
import biopax2cadbiom.commons as cm

LOGGER = cm.logger()


# Tests and params
# tuple: 0: graph_uris, 1: blacklist_file, 2: full_graph, 3: numeric_compartments
test_pool = {
    'homarus': (['http://biopax.org/lvl3', 'http://reactome.org/homarus'], None, True),
    'crithidia': (['http://biopax.org/lvl3', 'http://reactome.org/crithidia'], None, True),
    'vigna': (['http://biopax.org/lvl3', 'http://reactome.org/vigna'], None, True),
    'triticum': (['http://biopax.org/lvl3', 'http://reactome.org/triticum'], None, True),
    'cavia': (['http://biopax.org/lvl3', 'http://reactome.org/cavia'], None, True),
    'escherichia': (['http://biopax.org/lvl3', 'http://reactome.org/escherichia'], None, True, True),
    'cricetulus': (['http://biopax.org/lvl3', 'http://reactome.org/cricetulus'], None, True),
    'cricetulusWithoutSmallMolecules': (['http://biopax.org/lvl3', 'http://reactome.org/cricetulus'], DIR_TEST_CASES + 'blacklists/cricetulusSmallMolecules.csv', True, True),
    'mycobacterium': (['http://biopax.org/lvl3', 'http://reactome.org/mycobacterium'], None, True, True),
    'virtualCase1': (['http://biopax.org/lvl3', 'http://virtualcases.org/1'], None, True),
    'virtualCase2': (['http://biopax.org/lvl3', 'http://virtualcases.org/2'], None, True),
    'virtualCase1_2b': (['http://biopax.org/lvl3', 'http://virtualcases.org/1_2b'], None, True),
    'virtualCase3': (['http://biopax.org/lvl3', 'http://virtualcases.org/3'], None, False),
    'virtualCase4': (['http://biopax.org/lvl3', 'http://virtualcases.org/4'], None, True),
    'virtualCase5': (['http://biopax.org/lvl3', 'http://virtualcases.org/5'], None, True),
    'virtualCase6': (['http://biopax.org/lvl3', 'http://virtualcases.org/6'], None, False),
    'virtualCase6a': (['http://biopax.org/lvl3', 'http://virtualcases.org/6'], None, True),
    'virtualCase6b': (['http://biopax.org/lvl3', 'http://virtualcases.org/6b'], None, False),
    'virtualCase7': (['http://biopax.org/lvl3', 'http://virtualcases.org/7'], None, False),
    'virtualCase8': (['http://biopax.org/lvl3', 'http://virtualcases.org/8'], None, False),
    'virtualCase9': (['http://biopax.org/lvl3', 'http://virtualcases.org/9'], None, True),
    'virtualCase9b': (['http://biopax.org/lvl3', 'http://virtualcases.org/9b'], None, True),
    'virtualCase10b': (['http://biopax.org/lvl3', 'http://virtualcases.org/10b'], None, False),
    'virtualCase11': (['http://biopax.org/lvl3', 'http://virtualcases.org/11'], None, True),
    # 14: 14 with full_graph
    'virtualCase14': (['http://biopax.org/lvl3', 'http://virtualcases.org/14'], None, True),
    # 14b: without full_graph
    'virtualCase14b': (['http://biopax.org/lvl3', 'http://virtualcases.org/14'], None, False),
    # 14: without full_graph but tests the behaviour with an entity merge
    'virtualCase14c': (['http://biopax.org/lvl3', 'http://virtualcases.org/14c'], None, False),
    'virtualCase17': (['http://biopax.org/lvl3', 'http://virtualcases.org/17'], None, True),
    'virtualCase18': (['http://biopax.org/lvl3', 'http://virtualcases.org/18'], None, True),
    'virtualCase19': (['http://biopax.org/lvl3', 'http://virtualcases.org/19'], None, True),
    'virtualCase20': (['http://biopax.org/lvl3', 'http://virtualcases.org/20'], None, True),
    # controls
    'test_control_1': (['http://biopax.org/lvl3', 'http://virtualcases.org/test_control_1'], None, False),
    'test_control_2': (['http://biopax.org/lvl3', 'http://virtualcases.org/test_control_2'], None, True),
}

# Tests that raise Assertions
bad_test_pool = {
    'virtualCase10': (['http://biopax.org/lvl3', 'http://virtualcases.org/10'], None, False),
    'virtualCase10c': (['http://biopax.org/lvl3', 'http://virtualcases.org/10'], None, True),
}

## Helper functions ############################################################

[docs]def build_dynamical_system(model, reporter): """Build a dynamic system based on a given chart model This function is fully inspired from MCLAnalyser class. :param model: Chart model loaded from file :param reporter: Error reporter (check error attr to search if errors occured) :type model: <ChartModel> :type reporter: <Reporter> :return: Dynamic system with `tab_symb` attr initialized. This attr is mandatory to parse logical conditions in real conditions. :rtype: <CLDynSys> """ # Build CLDynSys (dynamical system in clause constraint form) # Parse the model data vtab = TableVisitor(reporter) model.accept(vtab) if reporter.error: return # PS: # Here, all keys of vtab.tab_symb = no_frontiers + frontiers + model name # (probably a bug for this last one) dynamic_system = CLDynSys(vtab.tab_symb, reporter) if reporter.error: return # Build clauses and auxiliary variables from events and places in the model comp_visitor = GT2Clauses(dynamic_system, reporter, True) model.accept(comp_visitor) if reporter.error: return return dynamic_system
[docs]def get_events_transitions(model_path): """Get sets of events and transitions from the given model .. note:: We are not interested in simple events (for the moment...); because the naming of events is not fixed in time. => only events with conditions are kept. :param model_path: Path of the model file (.bcx file). :type model_path: <str> :return: 2 sets: (events, transitions) :rtype: <tuple <set>, <set>> """ parser = MakeModelFromXmlFile(model_path) reporter = Reporter() dynamic_system = build_dynamical_system(parser.model, reporter) symb_table = dynamic_system.symb_tab # Loading of the model should be OK assert reporter.error == False LOGGER.debug("Loading %s done.", parser.model) LOGGER.debug("Symbol table:", symb_table) # Set of frozensets of event expressions events = set() # Set of all conditions in the model conditions = set() empty_conditions = 0 for transition in parser.model.transition_list: # print("cur event:", transition.event, "; cur trans:", transition.condition) # Handle conditions if transition.condition: tree = compile_cond(transition.condition, symb_table, reporter) conditions.add(tree) else: # Empty conditions are also important in order to compare 2 models conditions.add("empty_cond" + str(empty_conditions)) empty_conditions += 1 # Handle events tree, _, _ = compile_event(transition.event, symb_table, False, reporter) ret = get_default_blocks(tree) if ret: # We are not interested in simple events (for the moment...) # Keep the full set of expressions for each event (set of sets) events.add(frozenset(ret)) # No error should have been generated assert reporter.error == False return events, conditions
[docs]def get_default_blocks(item): """Return only right elements (conditions) of SigWhenExpr objects We decompile SigDefaultExpr objects until we get SigWhenExpr objects. Names of events (left elements of SigWhenExpr objects) are not interesting here. :param item: Boolean expression :type item: <SigExpression> (and its subtypes) """ if isinstance(item, SigDefaultExpr): sub_conds = [] sub_cond = get_default_blocks(item.left_h) if sub_cond: sub_conds += sub_cond sub_cond = get_default_blocks(item.right_h) if sub_cond: sub_conds += sub_cond return sub_conds if isinstance(item, SigWhenExpr): return [item.right_h] return None
## Dynamic loading of tests ####################################################
[docs]def t_model(model_name, *args): """Build model & check it vs a reference model. A fail of isomorphic test means that the set of nodes or the set of transitions is different than expected. A fail in the comparison of events or conditions means that the logical rules are different than expected. Please note that a comparison of events may fail while the isomorphic test doesn't. This may be due to variations in the writing of the formulas. Ex: These two sequences are identical: - `((_h_1_3) when ())` - `((_h_1_3))` ... but the first one generates an excess object <SigConstExpr True> for the empty `when` expression. Please also note that events are compared one by one; whereas conditions are compared accross the entire model. :param model_name: Reference model filename :param uris: Uris of graphs :param blacklist_file: :param convert_full_graph: If True, we decompose entities in classes even if they are not involved elsewhere. :key numeric_compartments: If True, names of compartments will be replaced by numbers. :type model_name: <str> :type uris: <list> :type blacklist_file: <str> :type convert_full_graph: <boolean> :type numeric_compartments: <boolean> """ uris = args[0] blacklist_file = args[1] convert_full_graph = args[2] numeric_compartments = False if len(args) < 4 else args[3] # Build parameters for biopax2cadbiom params = { "cadbiom_model": DIR_LOGS + "model.bcx", "full_graph": convert_full_graph, "graph_uris": uris, "backup_queries": False, "backup_file": DIR_LOGS + "backup.p", # osef, backup_queries = False "numeric_compartments": numeric_compartments, "blacklist_file": blacklist_file, "triplestore": SPARQL_PATH, "no_scc_fix": False, # Change this if you don't want SCC fix "limit": SPARQL_LIMIT, "provenance_uri": None, } b2c.main(params) # Build files path found_model = params["cadbiom_model"] ref_model = DIR_TEST_CASES + "refs/" + model_name + ".bcx" # Run isomorphic test between the 2 models (constructed and reference) check_state = graph_isomorph_test(found_model, ref_model) # Check if tests are ok for test, found_state in check_state.iteritems(): test_message = "{} isomorphic test failed for '{}' !".format( test.title(), model_name ) assert found_state == True, test_message # Check compliance of events and transitions found_events, found_conditions = get_events_transitions(found_model) expected_events, expected_conditions = get_events_transitions(ref_model) print("found events", found_events) print("expected events", expected_events) print("found conds", found_conditions) print("expected conds", expected_conditions) assert found_conditions == expected_conditions assert found_events == expected_events
[docs]def t_bad_model(model_name, *params): """Build model without valid transitions: raise an AssertionError""" try: t_model(model_name, *params) except AssertionError as ex: assert "No transitions found in the model !" in ex.message
[docs]def clean_test_env(dir): """Try to remove previous model & pickle file if they exist.""" try: os.remove(dir + "model.bcx") except OSError: pass try: os.remove(dir + "backup.p") except OSError: pass
[docs]@pytest.yield_fixture(autouse=True) def fixture_me(): """Fixture that is launched before and after all tests. .. note:: autouse allows to launch the fixture without explicitly noting it. """ clean_test_env(DIR_TEST_CASES)
for model_name_specie, params in test_pool.iteritems(): """Create test functions based on test_pool variable.""" func = partial(t_model, model_name_specie, *params) globals()["test_" + model_name_specie] = func for model_name_specie, params in bad_test_pool.iteritems(): """Create test functions based on t_bad_model variable.""" func = partial(t_bad_model, model_name_specie, *params) globals()["test_" + model_name_specie] = func