diff --git a/setup.py b/setup.py index c00478b..176a006 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ name='sqlwhat', version=version, packages=['sqlwhat', 'sqlwhat.checks'], - install_requires=['markdown2', 'antlr-plsql>=0.1.0', 'antlr-tsql>=0.1.0'], + install_requires=['protowhat', 'antlr-plsql>=0.1.0', 'antlr-tsql>=0.1.0'], description = 'Submission correctness tests for sql', author = 'Michael Chow', author_email = 'michael@datacamp.com', diff --git a/sqlwhat/Reporter.py b/sqlwhat/Reporter.py deleted file mode 100644 index 362578d..0000000 --- a/sqlwhat/Reporter.py +++ /dev/null @@ -1,91 +0,0 @@ -import re -import markdown2 -from sqlwhat.Test import TestFail, Test, Feedback - -""" -This file holds the reporter class. -""" - -class Reporter(object): - """Do reporting. - - This class holds the feedback- or success message and tracks whether there are failed tests - or not. All tests are executed trough do_test() in the Reporter. - """ - active_reporter = None - - def __init__(self, output = []): - self.failed_test = False - self.feedback = Feedback("Oh no, your solution is incorrect! Please, try again.") - self.success_msg = "Great work!" - self.errors_allowed = False - self.output = output - - self.raise_ast_pos_errors = False - - def set_tag(self, *args, **kwargs): pass - - def do_test(self, testobj, highlight=None): - """Do test. - - Execute a given test, unless some previous test has failed. If the test has failed, - the state of the reporter changes and the feedback is kept. - """ - - if isinstance(testobj, Test): - testobj.test() - result = testobj.result - if (not result): - self.failed_test = True - self.feedback = testobj.get_feedback() - - if highlight: - self.feedback = Feedback(self.feedback.message, highlight) - - raise TestFail(self.feedback) - - else: - result = None - testobj() # run function for side effects - - return result - - def get_error(self): - # each entry of output should be a dict of form, type: 'error', payload: 'somepayload' - return self.output[-1].get('payload') if self.output else None # get last error - - @staticmethod - def formatted_line_info(line_info): - cpy = {**line_info} - for k in ['column_start', 'column_end']: - if k in cpy: cpy[k] += 1 - return cpy - - - def build_payload(self, error=None): - error = self.get_error() if not error else error - - if (error is not None and not self.failed_test and not self.errors_allowed): - feedback_msg = "Your code contains an error: `%s`" % str(error) - return { - "correct": False, - "message": Reporter.to_html(feedback_msg) - } - - if self.failed_test: - return { - "correct": False, - "message": Reporter.to_html(self.feedback.message), - **self.formatted_line_info(self.feedback.line_info) - } - - else: - return { - "correct": True, - "message": Reporter.to_html(self.success_msg) - } - - @staticmethod - def to_html(msg): - return(re.sub("
(.*)
", "\\1", markdown2.markdown(msg)).strip()) - diff --git a/sqlwhat/State.py b/sqlwhat/State.py index c4de6f7..80239d1 100644 --- a/sqlwhat/State.py +++ b/sqlwhat/State.py @@ -2,71 +2,11 @@ import inspect from sqlwhat.selectors import Dispatcher +from protowhat.State import State as BaseState -class State: - def __init__(self, - student_code, - solution_code, - pre_exercise_code, - student_conn, - solution_conn, - student_result, - solution_result, - reporter, - solution_ast = None, - student_ast = None, - ast_dispatcher = None, - history = tuple()): +class State(BaseState): - for k,v in locals().items(): - if k != 'self': setattr(self, k, v) - - if ast_dispatcher is None: - # MCE doesn't always have connection - fallback on postgresql - dn = student_conn.dialect.name if student_conn else 'postgresql' - self.ast_dispatcher = Dispatcher.from_dialect(dn) - - # Parse solution and student code - # solution code raises an exception if can't be parsed - if solution_ast is None: self.solution_ast = self.ast_dispatcher.parse(solution_code) - if student_ast is None: self.student_ast = self.ast_dispatcher.parse(student_code) - - def get_ast_path(self): - rev_checks = filter(lambda x: x['type'] in ['check_field', 'check_node'], reversed(self.history)) - - try: - last = next(rev_checks) - if last['type'] == 'check_node': - # final check was for a node - return self.ast_dispatcher.describe(last['node'], - index = last['kwargs']['index'], - msg = "{index}{node_name}") - else: - node = next(rev_checks) - if node['type'] == 'check_node': - # checked for node, then for target, so can give rich description - return self.ast_dispatcher.describe(node['node'], - field = last['kwargs']['name'], - index = last['kwargs']['index'], - msg = "{index}{field_name} of the {node_name}") - except StopIteration: - return self.ast_dispatcher.describe(self.student_ast, "{node_name}") - - - def do_test(self, *args, highlight=None, **kwargs): - highlight = self.student_ast if highlight is None else highlight - - return self.reporter.do_test(*args, highlight=highlight, **kwargs) - - def to_child(self, **kwargs): - """Basic implementation of returning a child state""" - - good_pars = inspect.signature(self.__init__).parameters - bad_pars = set(kwargs) - set(good_pars) - if bad_pars: - raise KeyError("Invalid init params for State: %s"% ", ".join(bad_pars)) - - child = copy(self) - for k, v in kwargs.items(): setattr(child, k, v) - child.parent = self - return child + def get_dispatcher(self): + # MCE doesn't always have connection - fallback on postgresql + dialect = self.student_conn.dialect.name if self.student_conn else 'postgresql' + return Dispatcher.from_dialect(dialect) diff --git a/sqlwhat/Test.py b/sqlwhat/Test.py deleted file mode 100644 index 49925ab..0000000 --- a/sqlwhat/Test.py +++ /dev/null @@ -1,65 +0,0 @@ -import re -import _ast - -class Feedback(object): - - def __init__(self, message, astobj = None, strict=False): - self.message = message - self.line_info = {} - try: - if astobj is not None: - self.line_info = astobj._get_pos() - except Exception as e: - if strict: raise e - -class TestFail(Exception): - pass - -class Test(object): - """ - The basic Test. It should only contain a failure message, as all tests should result in - a failure message when they fail. - - Note: - This test should not be used by itself, subclasses should be used. - - Attributes: - feedback (str): A string containing the failure message in case the test fails. - result (bool): True if the test succeed, False if it failed. None if it hasn't been tested yet. - """ - - def __init__(self, feedback): - """ - Initialize the standard test. - - Args: - feedback: string or Feedback object - """ - if (issubclass(type(feedback), Feedback)): - self.feedback = feedback - elif (issubclass(type(feedback), str)): - self.feedback = Feedback(feedback) - else: - raise TypeError("When creating a test, specify either a string or a Feedback object") - - self.result = None - - def test(self): - """ - Wrapper around specific tests. Tests only get one chance. - """ - if self.result is None: - try: - self.specific_test() - self.result = np.array(self.result).all() - except: - self.result = False - - def specific_test(self): - """ - Perform the actual test. For the standard test, result will be set to False. - """ - self.result = False - - def get_feedback(self): - return(self.feedback) diff --git a/sqlwhat/checks/__init__.py b/sqlwhat/checks/__init__.py index ac9740b..8c8ef6a 100644 --- a/sqlwhat/checks/__init__.py +++ b/sqlwhat/checks/__init__.py @@ -1,3 +1,4 @@ from sqlwhat.checks.check_result import check_result, test_has_columns, test_nrows, test_ncols, test_column, allow_error, test_error, test_name_miscased, test_column_name, sort_rows -from sqlwhat.checks.check_logic import fail, multi, extend, test_or, test_correct from sqlwhat.checks.check_funcs import check_node, check_field, test_student_typed, has_equal_ast, test_mc, success_msg, verify_ast_parses + +from protowhat.checks.check_logic import fail, multi, extend, test_or, test_correct diff --git a/sqlwhat/checks/check_funcs.py b/sqlwhat/checks/check_funcs.py index bf5f0fe..4cbdeaf 100644 --- a/sqlwhat/checks/check_funcs.py +++ b/sqlwhat/checks/check_funcs.py @@ -1,4 +1,3 @@ -from sqlwhat.Test import TestFail, Test from sqlwhat.State import State from functools import partial, wraps @@ -61,7 +60,7 @@ def check_node(state, name, index=0, missing_msg="Could not find the {index}{nod except IndexError: # use speaker on ast dialect module to get message, or fall back to generic _msg = state.ast_dispatcher.describe(sol_stmt, missing_msg, index = index) - state.do_test(Test(_msg or MSG_CHECK_FALLBACK)) + state.do_test(_msg or MSG_CHECK_FALLBACK) action = {'type': 'check_node', 'kwargs': {'name': name, 'index': index}, 'node': stu_stmt} @@ -108,12 +107,12 @@ def check_field(state, name, index=None, missing_msg="Could not find the {index} except: # use speaker on ast dialect module to get message, or fall back to generic _msg = state.ast_dispatcher.describe(state.student_ast, missing_msg, field = name, index = index) - state.do_test(Test(_msg or MSG_CHECK_FALLBACK)) + state.do_test(_msg or MSG_CHECK_FALLBACK) # fail if attribute exists, but is none only for student if stu_attr is None and sol_attr is not None: _msg = state.ast_dispatcher.describe(state.student_ast, missing_msg, field = name, index = index) - state.do_test(Test(_msg)) + state.do_test(_msg) action = {'type': 'check_field', 'kwargs': {'name': name, 'index': index}} @@ -176,7 +175,7 @@ def test_student_typed(state, text, msg="Submission does not contain the code `{ res = text in stu_text if fixed else re.search(text, stu_text) if not res: - state.do_test(Test(_msg)) + state.do_test(_msg) return state @@ -221,8 +220,8 @@ def has_equal_ast(state, sol_rep = repr(sol_ast) _msg = msg.format(ast_path = state.get_ast_path()) - if exact and (sol_rep != stu_rep): state.do_test(Test(_msg or MSG_CHECK_FALLBACK)) - elif not exact and (sol_rep not in stu_rep): state.do_test(Test(_msg or MSG_CHECK_FALLBACK)) + if exact and (sol_rep != stu_rep): state.do_test(_msg or MSG_CHECK_FALLBACK) + elif not exact and (sol_rep not in stu_rep): state.do_test(_msg or MSG_CHECK_FALLBACK) return state @@ -247,7 +246,7 @@ def test_mc(state, correct, msgs): exec(state.student_code, globals(), ctxt) sel_indx = ctxt['selected_option'] if sel_indx != correct: - state.do_test(Test(msgs[sel_indx-1])) + state.do_test(msgs[sel_indx-1]) else: state.reporter.success_msg = msgs[correct-1] @@ -274,6 +273,6 @@ def success_msg(state, msg): def verify_ast_parses(state): asts = [state.student_ast, state.solution_ast] if any(isinstance(c, state.ast_dispatcher.ast.AntlrException) for c in asts): - state.do_test(Test("AST did not parse")) + state.do_test("AST did not parse") return state diff --git a/sqlwhat/checks/check_logic.py b/sqlwhat/checks/check_logic.py index 4509c81..be94ae8 100644 --- a/sqlwhat/checks/check_logic.py +++ b/sqlwhat/checks/check_logic.py @@ -1,10 +1,10 @@ -from sqlwhat.Test import TestFail, Test +from protowhat.Test import TestFail from types import GeneratorType from functools import partial def fail(state, msg=""): """Always fails the SCT, with an optional msg.""" - state.do_test(Test(msg)) + state.do_test(msg) return state diff --git a/sqlwhat/checks/check_result.py b/sqlwhat/checks/check_result.py index 03edf96..7182ca9 100644 --- a/sqlwhat/checks/check_result.py +++ b/sqlwhat/checks/check_result.py @@ -1,5 +1,3 @@ -from sqlwhat.Test import TestFail, Test - def allow_error(state): """Allow submission to pass, even if it originally caused a database error.""" @@ -13,7 +11,7 @@ def test_error(state, msg="Your command returned the following error: {}"): error = state.reporter.get_error() if error is not None: - state.do_test(Test(msg.format(error))) + state.do_test(msg.format(error)) return state @@ -39,7 +37,7 @@ def test_has_columns(state, msg="Your result did not output any columns."): """Test if the student's query result contains any columns""" if not state.student_result: - state.do_test(Test(msg)) + state.do_test(msg) return state @@ -55,7 +53,7 @@ def test_nrows(state, msg="Result has {} row(s) but expected {}."): if n_stu != n_sol: _msg = msg.format(n_stu, n_sol) - state.do_test(Test(_msg)) + state.do_test(_msg) return state @@ -70,7 +68,7 @@ def test_ncols(state, msg="Result has {} column(s) but expected {}."): if n_stu != n_sol: _msg = msg.format(n_stu, n_sol) - state.do_test(Test(_msg)) + state.do_test(_msg) return state @@ -86,7 +84,7 @@ def test_name_miscased(state, name, if name.lower() in stu_lower and name not in stu_res: _msg = msg.format(stu_lower[name.lower()], name) - state.do_test(Test(_msg)) + state.do_test(_msg) return state @@ -107,7 +105,7 @@ def test_column_name(state, name, if name.lower() not in stu_lower: _msg = msg.format(name) - state.do_test(Test(_msg)) + state.do_test(_msg) return state @@ -171,7 +169,7 @@ def test_column(state, name, msg="Column `{}` in the solution does not have a co # fail test if no match _msg = msg.format(name) - state.do_test(Test(_msg)) + state.do_test(_msg) # return state just in case, but should never happen return state diff --git a/sqlwhat/sct_syntax.py b/sqlwhat/sct_syntax.py index 4bde460..fd2f2d6 100644 --- a/sqlwhat/sct_syntax.py +++ b/sqlwhat/sct_syntax.py @@ -1,138 +1,17 @@ -from sqlwhat.State import State -import copy -from functools import wraps, reduce - -def state_dec(f): - """Decorate check_* functions to return F chain if no state passed""" - - @wraps(f) - def wrapper(*args, **kwargs): - state = kwargs.get('state', args[0] if len(args) else None) - if isinstance(state, State): - return f(*args, **kwargs) - else: - return F._from_func(f, *args, **kwargs) - - return wrapper - -class Chain: - def __init__(self, state): - self._state = state - self._crnt_sct = None - self._waiting_on_call = False - - def _double_attr_error(self): - raise AttributeError("Did you forget to call a statement? " - "e.g. Ex().check_list_comp.check_body()") - - def __getattr__(self, attr): - if attr not in ATTR_SCTS: raise AttributeError("No SCT named %s"%attr) - elif self._waiting_on_call: self._double_attr_error() - else: - # make a copy to return, - # in case someone does: a = chain.a; b = chain.b - return self._sct_copy(ATTR_SCTS[attr]) - - def __call__(self, *args, **kwargs): - # NOTE: the only change from python what is that state is now 1st pos arg below - self._state = self._crnt_sct(self._state, *args, **kwargs) - self._waiting_on_call = False - return self - - def __rshift__(self, f): - if self._waiting_on_call: - self._double_attr_error() - elif type(f) == Chain: - raise BaseException("did you use a result of the Ex() function on the right hand side of the + operator?") - elif not callable(f): - raise BaseException("right hand side of + operator should be an SCT, so must be callable!") - else: - chain = self._sct_copy(f) - return chain() - - def _sct_copy(self, f): - chain = copy.copy(self) - chain._crnt_sct = f - chain._waiting_on_call = True - return chain - - - -class F(Chain): - def __init__(self, stack = None): - self._crnt_sct = None - self._stack = [] if stack is None else stack - self._waiting_on_call = False - - def __call__(self, *args, **kwargs): - if not self._crnt_sct: - state = kwargs.get('state') or args[0] - return reduce(lambda s, cd: self._call_from_data(*cd, state=s), self._stack, state) - else: - call_data = (self._crnt_sct, args, kwargs) - return self.__class__(self._stack + [call_data]) - - @staticmethod - def _call_from_data(f, args, kwargs, state): - return f(state, *args, **kwargs) - - @classmethod - def _from_func(cls, f, *args, **kwargs): - """Creates a function chain starting with the specified SCT (f), and its arguments.""" - func_chain = cls() - func_chain._stack.append([f, args, kwargs]) - return func_chain - - -def Ex(state=None): - """Returns the current code state as a Chain instance. - - Args: - state: a State instance, which contains the student/solution code and results. - - This allows SCTs to be run without including their 1st argument, ``state``. - - Note: - When writing SCTs on DataCamp, no State argument to ``Ex`` is necessary. - The exercise State is built for you. - - :Example: - - :: - - # life without Ex - state = SomeStateProducingFunction() - test_student_typed(state, text="SELECT id") # some SCT, w/state as first arg - - # life with Ex - state = SomeStateProducingFunction() - Ex(state).test_student_typed(text="SELECT id") # some SCT, w/o state as arg - - # life writing SCTs on DataCamp.com - Ex().test_student_typed(text="SELECT id") - - Further, note that the operator ``>>`` can be used in place of chaining.:: - - # Ex with chaining - Ex().test_student_typed(text="SELECT id") - - # Ex without - Ex() >> test_student_typed(text="SELECT id") - - """ - return Chain(state or State.root_state) - # Wrap SCT checks ------------------------------------------------------------- + +from sqlwhat.State import State from sqlwhat import checks import builtins +from protowhat.sct_syntax import create_sct_context + # used in Chain and F, to know what methods are available -ATTR_SCTS = {k: v for k,v in vars(checks).items() if k not in builtins.__dict__ if not k.startswith('__')} +sct_dict = {k: v for k,v in vars(checks).items() if k not in builtins.__dict__ if not k.startswith('__')} +SCT_CTX = create_sct_context(State, sct_dict) + # used in test_exercise, so that scts without Ex() don't run immediately -SCT_CTX = {k: state_dec(v) for k,v in ATTR_SCTS.items()} + globals().update(SCT_CTX) -SCT_CTX['Ex'] = Ex -SCT_CTX['F'] = F -SCT_CTX['state_dec'] = state_dec # put on module for easy importing __all__ = list(SCT_CTX.keys()) diff --git a/sqlwhat/test_exercise.py b/sqlwhat/test_exercise.py index d396b0f..91458e3 100644 --- a/sqlwhat/test_exercise.py +++ b/sqlwhat/test_exercise.py @@ -1,6 +1,6 @@ from sqlwhat.State import State -from sqlwhat.Test import TestFail -from sqlwhat.Reporter import Reporter +from protowhat.Test import TestFail +from protowhat.Reporter import Reporter from sqlwhat.sct_syntax import SCT_CTX def test_exercise(sct, @@ -18,7 +18,6 @@ def test_exercise(sct, """ """ - # TODO: put reporter on state state = State( student_code = student_code, solution_code = solution_code, @@ -29,7 +28,7 @@ def test_exercise(sct, solution_result = solution_result, reporter = Reporter(error)) - State.root_state = state + SCT_CTX['Ex'].root_state = state try: exec(sct, SCT_CTX) diff --git a/sqlwhat/tests/test_check_funcs.py b/sqlwhat/tests/test_check_funcs.py index 31863a5..6a576d2 100644 --- a/sqlwhat/tests/test_check_funcs.py +++ b/sqlwhat/tests/test_check_funcs.py @@ -2,8 +2,8 @@ from sqlwhat.checks import check_funcs as cf from sqlwhat.selectors import get_ast_parser, Dispatcher from sqlwhat.State import State -from sqlwhat.Reporter import Reporter -from sqlwhat.Test import TestFail as TF +from protowhat.Reporter import Reporter +from protowhat.Test import TestFail as TF import pytest def print_message(exc): print(exc.value.args[0].message) diff --git a/sqlwhat/tests/test_check_logic.py b/sqlwhat/tests/test_check_logic.py index 2be84a5..5d74986 100644 --- a/sqlwhat/tests/test_check_logic.py +++ b/sqlwhat/tests/test_check_logic.py @@ -1,8 +1,8 @@ import pytest from sqlwhat.State import State from sqlwhat.checks import check_logic as cl -from sqlwhat.Reporter import Reporter -from sqlwhat.Test import TestFail as TF +from protowhat.Reporter import Reporter +from protowhat.Test import TestFail as TF from helper import Connection from functools import partial diff --git a/sqlwhat/tests/test_check_result.py b/sqlwhat/tests/test_check_result.py index ab48f32..f6d9a12 100644 --- a/sqlwhat/tests/test_check_result.py +++ b/sqlwhat/tests/test_check_result.py @@ -2,8 +2,8 @@ from sqlwhat.State import State import importlib cr = importlib.import_module('sqlwhat.checks.check_result') -from sqlwhat.Reporter import Reporter -from sqlwhat.Test import TestFail as TF +from protowhat.Reporter import Reporter +from protowhat.Test import TestFail as TF from helper import Connection def prepare_state(sol_result, stu_result): diff --git a/sqlwhat/tests/test_feedback.py b/sqlwhat/tests/test_feedback.py index c1b08ea..7acb235 100644 --- a/sqlwhat/tests/test_feedback.py +++ b/sqlwhat/tests/test_feedback.py @@ -1,5 +1,5 @@ -from sqlwhat.Test import Feedback, TestFail as TF, Test as _Test -from sqlwhat.Reporter import Reporter +from protowhat.Test import Feedback, TestFail as TF +from protowhat.Reporter import Reporter from sqlwhat.State import State from sqlwhat.selectors import Dispatcher from antlr_tsql import ast @@ -42,7 +42,7 @@ def test_state_line_info(): ast_dispatcher = Dispatcher.from_dialect('mssql')) with pytest.raises(TF): - state.do_test(_Test("failure message")) + state.do_test("failure message") payload = state.reporter.build_payload() diff --git a/sqlwhat/tests/test_sct_syntax.py b/sqlwhat/tests/test_sct_syntax.py index c5e6525..8008d01 100644 --- a/sqlwhat/tests/test_sct_syntax.py +++ b/sqlwhat/tests/test_sct_syntax.py @@ -1,6 +1,10 @@ -from sqlwhat.sct_syntax import Ex, F, state_dec +from protowhat.State import State +from protowhat.sct_syntax import ExGen, F, state_dec_gen import pytest +state_dec = state_dec_gen(State, {}) +Ex = ExGen(State, {}) + @pytest.fixture def addx(): return lambda state, x: state + x @@ -64,8 +68,7 @@ def test_f_add_ex_err(f, ex): with pytest.raises(BaseException): f >> ex -from sqlwhat.State import State -from sqlwhat.Reporter import Reporter +from protowhat.Reporter import Reporter def test_state_dec_instant_eval(): state = State("student_code", "", "", None, None, {}, {}, Reporter()) diff --git a/sqlwhat/tests/test_selectors.py b/sqlwhat/tests/test_selectors.py index eb01611..2c6fc0f 100644 --- a/sqlwhat/tests/test_selectors.py +++ b/sqlwhat/tests/test_selectors.py @@ -1,7 +1,7 @@ from sqlwhat.selectors import Selector, Dispatcher, get_ast_parser from sqlwhat.State import State -from sqlwhat.Reporter import Reporter -from sqlwhat.Test import TestFail as TF +from protowhat.Reporter import Reporter +from protowhat.Test import TestFail as TF import pytest @pytest.fixture diff --git a/sqlwhat/tests/test_state.py b/sqlwhat/tests/test_state.py index d2b6444..1934df5 100644 --- a/sqlwhat/tests/test_state.py +++ b/sqlwhat/tests/test_state.py @@ -1,7 +1,7 @@ from sqlwhat.sct_syntax import Ex from sqlwhat.State import State -from sqlwhat.Reporter import Reporter -from sqlwhat.Test import TestFail as TF +from protowhat.Reporter import Reporter +from protowhat.Test import TestFail as TF from helper import Connection import pytest @@ -20,7 +20,7 @@ def test_pass(conn): solution_conn = None, reporter= Reporter()) - State.root_state = state + Ex.root_state = state assert Ex().check_result() @@ -35,7 +35,7 @@ def test_fail(conn): solution_conn = None, reporter= Reporter()) - State.root_state = state + Ex.root_state = state with pytest.raises(TF): Ex().check_result() diff --git a/sqlwhat/tests/test_success_msg.py b/sqlwhat/tests/test_success_msg.py index 59e6a49..17aa3f8 100644 --- a/sqlwhat/tests/test_success_msg.py +++ b/sqlwhat/tests/test_success_msg.py @@ -2,8 +2,8 @@ from sqlwhat.checks.check_logic import fail from sqlwhat.sct_syntax import Ex from sqlwhat.State import State -from sqlwhat.Reporter import Reporter -from sqlwhat.Test import TestFail as TF +from protowhat.Reporter import Reporter +from protowhat.Test import TestFail as TF import pytest def prepare_state(student_code): diff --git a/sqlwhat/tests/test_test_mc.py b/sqlwhat/tests/test_test_mc.py index 6d97254..198ea09 100644 --- a/sqlwhat/tests/test_test_mc.py +++ b/sqlwhat/tests/test_test_mc.py @@ -1,8 +1,8 @@ from sqlwhat.checks import test_mc as _test_mc from sqlwhat.sct_syntax import Ex from sqlwhat.State import State -from sqlwhat.Reporter import Reporter -from sqlwhat.Test import TestFail as TF +from protowhat.Reporter import Reporter +from protowhat.Test import TestFail as TF import pytest def prepare_state(student_code):