Как проверить использование сравнения? - PullRequest
2 голосов
/ 29 апреля 2019

Как можно проверить, что сравнение используется для получения результата?

x = 42
y = 43
is_identical = id(x) == id(y)

и чтобы не было написано что-то вроде этого, чтобы обмануть тест:

is_identical = False

Обновление :

Исходная информация заключается в том, что учащиеся должны загрузить свои домашние задания в инструмент, который автоматически запускает модульные тесты (или пестесты), чтобы показать им, насколько хорош загруженный код.В одном упражнении они должны просто выполнить сравнение и не быть в состоянии обмануть модульный тест, написав True / False непосредственно в качестве присваивания переменной.Функции пока не используются.

Ответы [ 3 ]

2 голосов
/ 29 апреля 2019

Казалось, что эта хакерская мартышка сработала:

# test_foo.py

from unittest import TestCase
from unittest.mock import patch

from importlib import reload

class FooTest(TestCase):

    def test_comparison_used(self):
        import foo
        self.assertFalse(foo.is_identical)
        with patch("foo.id") as id_:
            reload(foo)
            id_().__eq__.assert_called()

Когда foo.py использует метод "читерства":

#!/usr/bin/env python

x = 42
y = 43
is_identical = False # id(x) == id(y)

Я получаю E AssertionError: Expected '__eq__' to have been called.

но если я сделаю is_identical = id(x) == id(y), то тест пройден.

1 голос
/ 29 апреля 2019

Следующее решение использует константы в объектах кода для определения значений bool True / False и инструкцию байт-кода 'COMPARE_OP' для идентификации операции сравнения.Код может быть улучшен, чтобы добавить больше случаев, но это просто, чтобы дать вам преимущество.Чтобы показать все условия, я установил все аргументы в функции как необязательные:

In [130]: def catch_trick_bool(unit_test_result=None, student_compare=None,student_trick=None):
     ...:     student = str(input("Enter student name: "))
     ...:     # import dis - Use this to further examine code objects.
     ...:     # Eg. use  dis.dis(co_unit_test) to view the code objects for 
     ...:     # the unit test expresssion
     ...:     if unit_test_result:
     ...:         co_unit_test = compile(unit_test_result, 'none', 'single')
     ...:         if False in co_unit_test.co_consts:
     ...:             print("Bool value 'False' returned from Unit Test")
     ...:     elif student_compare:
     ...:         co_student_compare = compile(student_compare, 'none', 'single')
     ...:         if '6b' in co_student_compare.co_code.hex():
     ...:             print("Student {} performed the comparison successfully".format(student))
     ...:     else:
     ...:         co_student_trick = compile(student_trick, 'none', 'single')
     ...:         if False in co_student_trick.co_consts:
     ...:             print("Student {} tried to set a bool value of 'False' for the test".format(student))
     ...:

In [131]: catch_trick_bool(unit_test_result='is_identical_unit_test = False')
Enter student name: John
Bool value 'False' returned from Unit Test

In [132]: catch_trick_bool(student_trick='is_identical_student_trick = False')
Enter student name: John
Student John tried to set a bool value of 'False' for the test

In [133]: catch_trick_bool(student_compare='id(x) == id(y)')
Enter student name: John
Student John performed the comparison successfully

Пояснение :

Ниже показано, как шестнадцатеричный байт-код 0x6b ищется в шестнадцатеричном представлении инструкции байт-кода для объекта кода.Этот байт-код соответствует операционному имени 'COMPARE_OP'.Эта инструкция указывает на операцию сравнения в коде.

In [137]: co = compile('is_identical = id(x) == id(y)', 'none', 'single')

In [138]: type(co)
Out[138]: code

In [139]: dis.dis(co)
  1           0 LOAD_NAME                0 (id)
              2 LOAD_NAME                1 (x)
              4 CALL_FUNCTION            1
              6 LOAD_NAME                0 (id)
              8 LOAD_NAME                2 (y)
             10 CALL_FUNCTION            1
             12 COMPARE_OP               2 (==)
             14 STORE_NAME               3 (is_identical)
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

In [140]: co.co_code
Out[140]: b'e\x00e\x01\x83\x01e\x00e\x02\x83\x01k\x02Z\x03d\x00S\x00'

In [141]: co.co_code.hex()
Out[141]: '6500650183016500650283016b025a0364005300'

In [142]: dis.opname[0x6b]
Out[142]: 'COMPARE_OP'

In [143]: '6b' in co.co_code.hex()
Out[143]: True

In [144]: co2 = compile('is_identical = 5 > 2', 'none', 'single')

In [145]: co2.co_code.hex()
Out[145]: '640064016b045a0064025300'

In [146]: '6b' in co2.co_code.hex()
Out[146]: True

Аналогично, значения bool преобразуются в инструкции LOAD_CONST bytecode, к которым можно легко получить доступ с помощью co_consts:

In [147]: co3 = compile('is_identical = False', 'none', 'single')

In [148]: dis.dis(co3)
  1           0 LOAD_CONST               0 (False)
              2 STORE_NAME               0 (is_identical)
              4 LOAD_CONST               1 (None)
              6 RETURN_VALUE

In [149]: co3.co_consts
Out[149]: (False, None)

In [150]: False in co3.co_consts
Out[150]: True

Заключение :

Объекты дизассемблированного кода могут помочь определить источник значения bool.Если оно явно определено, как в приведенном выше случае, значение bool будет разрешено как константа LOAD_CONST инструкция байт-кода.Если он выводится неявно через логическое выражение, которое использует сравнение (например, 5> 2), код будет преобразован в конкретную инструкцию байт-кода COMPARE_OP, чтобы указать операцию сравнения.Вы можете дополнительно расширить код, чтобы также проверить достоверность значений и изменить его в соответствии с вашими требованиями.

0 голосов
/ 30 апреля 2019

Я удовлетворен ответом от amanb .Тем не менее, тем временем я также нашел решение, которое дало мне хорошие результаты.Вместо библиотеки dis я использовал ast.

def testcase_ok():
    import ast
    to_proof = "is_identical = id(x) == id(y)"  # will pass
    assert any(isinstance(node, ast.Eq) for node in ast.walk(ast.parse(to_proof)))


def testcase_fail():
    import ast
    to_proof = "is_identical = False"  # will fail
    assert any(isinstance(node, ast.Eq) for node in ast.walk(ast.parse(to_proof)))

Кроме того, это один из способов получить соответствующую часть исходного кодабез жесткого кодирования is_identical = … (без проверки, если задана переменная, для ясности):

import inspect
import file_to_test as submission

codelines = inspect.getsourcelines(submission)[0]
to_proof = [line.strip() for line in codelines if line.strip().lower().startswith("is_identical")][-1]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...