You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
9.3 KiB

import json
import unittest
from argparse import ArgumentParser
from contextlib import contextmanager
from pprint import pprint
from typing import Dict, List
from unittest.mock import MagicMock, patch
import sys
import shlex
from wx._core import CommandEvent
from gooey import GooeyParser
from python_bindings.coms import decode_payload, deserialize_inbound
from python_bindings.dynamics import patch_argument, check_value
from gooey.python_bindings import control
from gooey.python_bindings.parameters import gooey_params
from gooey.gui import state as s
from gooey.python_bindings.schema import validate_public_state
from python_bindings.types import FormField
from tests.harness import instrumentGooey
from gooey.tests import *
def custom_type(x):
if x == '1234':
return x
else:
raise Exception('KABOOM!')
class TestControl(unittest.TestCase):
def tearDown(self):
"""
Undoes the monkey patching after every tests
"""
if hasattr(ArgumentParser, 'original_parse_args'):
ArgumentParser.parse_args = ArgumentParser.original_parse_args
def test_validate_form(self):
"""
Testing the major validation cases we support.
"""
writer = MagicMock()
exit = MagicMock()
monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit)
ArgumentParser.original_parse_args = ArgumentParser.parse_args
ArgumentParser.parse_args = monkey_patch
parser = GooeyParser()
# examples:
# ERROR: mismatched builtin type
parser.add_argument('a', type=int, gooey_options={'initial_value': 'not-an-int'})
# ERROR: mismatched custom type
parser.add_argument('b', type=custom_type, gooey_options={'initial_value': 'not-a-float'})
# ERROR: missing required positional arg
parser.add_argument('c')
# ERROR: missing required 'optional' arg
parser.add_argument('--oc', required=True)
# VALID: This is one of the bizarre cases which are possible
# but don't make much sense. It should pass through as valid
# because there's no way for us to send a 'not present optional value'
parser.add_argument('--bo', action='store_true', required=True)
# ERROR: a required mutex group, with no args supplied.
# Should flag all as missing.
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--gp1-a', type=str)
group.add_argument('--gp1-b', type=str)
# ERROR: required mutex group with a default option but nothing
# selected will still fail
group2 = parser.add_mutually_exclusive_group(required=True)
group2.add_argument('--gp2-a', type=str)
group2.add_argument('--gp2-b', type=str, default='Heeeeyyyyy')
# VALID: now, same as above, but now the option is actually enabled via
# the initial selection. No error.
group3 = parser.add_mutually_exclusive_group(required=True, gooey_options={'initial_selection': 1})
group3.add_argument('--gp3-a', type=str)
group3.add_argument('--gp3-b', type=str, default='Heeeeyyyyy')
# VALID: optional mutex.
group4 = parser.add_mutually_exclusive_group()
group4.add_argument('--gp4-a', type=str)
group4.add_argument('--gp4-b', type=str)
# VALID: arg present and type satisfied
parser.add_argument('ga', type=str, gooey_options={'initial_value': 'whatever'})
# VALID: arg present and custom type satisfied
parser.add_argument('gb', type=custom_type, gooey_options={'initial_value': '1234'})
# VALID: optional
parser.add_argument('--gc')
# now we're adding the same
with instrumentGooey(parser, target='test') as (app, frame, gapp):
# we start off with no errors
self.assertFalse(s.has_errors(gapp.fullState()))
# now we feed our form-validation
cmd = s.buildFormValidationCmd(gapp.fullState())
asdf = shlex.split(cmd)[1:]
parser.parse_args(shlex.split(cmd)[1:])
assert writer.called
assert exit.called
result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')
# Host->Gooey communication is all done over the PublicGooeyState schema
# as such, we coarsely validate it's shape here
validate_public_state(result)
# manually merging the two states back together
nextState = s.mergeExternalState(gapp.fullState(), result)
# and now we find that we have errors!
self.assertTrue(s.has_errors(nextState))
items = s.activeFormState(nextState)
self.assertIn('invalid literal', get_by_id(items, 'a')['error'])
self.assertIn('KABOOM!', get_by_id(items, 'b')['error'])
self.assertIn('required', get_by_id(items, 'c')['error'])
self.assertIn('required', get_by_id(items, 'oc')['error'])
for item in get_by_id(items, 'group_gp1_a_gp1_b')['options']:
self.assertIsNotNone(item['error'])
for item in get_by_id(items, 'group_gp2_a_gp2_b')['options']:
self.assertIsNotNone(item['error'])
for item in get_by_id(items, 'group_gp3_a_gp3_b')['options']:
self.assertIsNone(item['error'])
# should be None, since this one was entirely optional
for item in get_by_id(items, 'group_gp4_a_gp4_b')['options']:
self.assertIsNone(item['error'])
self.assertIsNone(get_by_id(items, 'bo')['error'])
self.assertIsNone(get_by_id(items, 'ga')['error'])
self.assertIsNone(get_by_id(items, 'gb')['error'])
self.assertIsNone(get_by_id(items, 'gc')['error'])
def test_subparsers(self):
"""
Making sure that subparsers are handled correctly and
all validations still work as expected.
"""
writer = MagicMock()
exit = MagicMock()
monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit)
ArgumentParser.original_parse_args = ArgumentParser.parse_args
ArgumentParser.parse_args = monkey_patch
def build_parser():
# we build a new parser for each subtest
# since we monkey patch the hell out of it
# each time
parser = GooeyParser()
subs = parser.add_subparsers()
foo = subs.add_parser('foo')
foo.add_argument('a')
foo.add_argument('b')
foo.add_argument('p')
bar = subs.add_parser('bar')
bar.add_argument('a')
bar.add_argument('b')
bar.add_argument('z')
return parser
parser = build_parser()
with instrumentGooey(parser, target='test') as (app, frame, gapp):
with self.subTest('first subparser'):
# we start off with no errors
self.assertFalse(s.has_errors(gapp.fullState()))
cmd = s.buildFormValidationCmd(gapp.fullState())
parser.parse_args(shlex.split(cmd)[1:])
assert writer.called
assert exit.called
result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')
nextState = s.mergeExternalState(gapp.fullState(), result)
# by default, the subparser defined first, 'foo', is selected.
self.assertIn('foo', nextState['forms'])
# and we should find its attributes
expected = {'a', 'b', 'p'}
actual = {x['id'] for x in nextState['forms']['foo']}
self.assertEqual(expected, actual)
parser = build_parser()
with instrumentGooey(parser, target='test') as (app, frame, gapp):
with self.subTest('Second subparser'):
# mocking a 'selection change' event to select
# the second subparser
event = MagicMock()
event.Selection = 1
gapp.handleSelectAction(event)
# Flushing our events by running the main loop
wx.CallLater(1, app.ExitMainLoop)
app.MainLoop()
cmd = s.buildFormValidationCmd(gapp.fullState())
parser.parse_args(shlex.split(cmd)[1:])
assert writer.called
assert exit.called
result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')
nextState = s.mergeExternalState(gapp.fullState(), result)
# Now our second subparer, 'bar', should be present.
self.assertIn('bar', nextState['forms'])
# and we should find its attributes
expected = {'a', 'b', 'z'}
actual = {x['id'] for x in nextState['forms']['bar']}
self.assertEqual(expected, actual)
def test_ignore_gooey(self):
parser = GooeyParser()
subs = parser.add_subparsers()
foo = subs.add_parser('foo')
foo.add_argument('a')
foo.add_argument('b')
foo.add_argument('p')
bar = subs.add_parser('bar')
bar.add_argument('a')
bar.add_argument('b')
bar.add_argument('z')
control.bypass_gooey(gooey_params())(parser)
def get_by_id(items: List[FormField], id: str):
return [x for x in items if x['id'] == id][0]