1437 lines
39 KiB
Python
1437 lines
39 KiB
Python
from path import Path
|
|
import os, sys, asyncio
|
|
|
|
if sys.platform == 'win32':
|
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
|
|
from multiprocessing import Process
|
|
|
|
import pytest
|
|
import pytestqt
|
|
import cadquery as cq
|
|
|
|
from PyQt5.QtCore import Qt, QSettings
|
|
from PyQt5.QtWidgets import QFileDialog, QMessageBox
|
|
|
|
from cq_editor.__main__ import MainWindow
|
|
from cq_editor.widgets.editor import Editor
|
|
from cq_editor.cq_utils import export, get_occ_color
|
|
|
|
code = \
|
|
'''import cadquery as cq
|
|
result = cq.Workplane("XY" )
|
|
result = result.box(3, 3, 0.5)
|
|
result = result.edges("|Z").fillet(0.125)'''
|
|
|
|
code_bigger_object = \
|
|
'''import cadquery as cq
|
|
result = cq.Workplane("XY" )
|
|
result = result.box(20, 20, 0.5)
|
|
result = result.edges("|Z").fillet(0.125)
|
|
'''
|
|
|
|
code_show_Workplane = \
|
|
'''import cadquery as cq
|
|
result = cq.Workplane("XY" )
|
|
result = result.box(3, 3, 0.5)
|
|
result = result.edges("|Z").fillet(0.125)
|
|
|
|
show_object(result)
|
|
'''
|
|
|
|
code_show_Workplane_named = \
|
|
'''import cadquery as cq
|
|
result = cq.Workplane("XY" )
|
|
result = result.box(3, 3, 0.5)
|
|
result = result.edges("|Z").fillet(0.125)
|
|
|
|
log('test')
|
|
show_object(result,name='test')
|
|
'''
|
|
|
|
code_show_Shape = \
|
|
'''import cadquery as cq
|
|
result = cq.Workplane("XY" )
|
|
result = result.box(3, 3, 0.5)
|
|
result = result.edges("|Z").fillet(0.125)
|
|
|
|
show_object(result.val())
|
|
'''
|
|
|
|
code_debug_Workplane = \
|
|
'''import cadquery as cq
|
|
result = cq.Workplane("XY" )
|
|
result = result.box(3, 3, 0.5)
|
|
result = result.edges("|Z").fillet(0.125)
|
|
|
|
debug(result)
|
|
'''
|
|
|
|
code_multi = \
|
|
'''import cadquery as cq
|
|
result1 = cq.Workplane("XY" ).box(3, 3, 0.5)
|
|
result2 = cq.Workplane("XY" ).box(3, 3, 0.5).translate((0,15,0))
|
|
'''
|
|
|
|
code_nested_top = """import test_nested_bottom
|
|
"""
|
|
|
|
code_nested_bottom = """a=1
|
|
"""
|
|
|
|
code_reload_issue = """wire0 = cq.Workplane().lineTo(5, 5).lineTo(10, 0).close().val()
|
|
solid1 = cq.Solid.extrudeLinear(cq.Face.makeFromWires(wire0), cq.Vector(0, 0, 1))
|
|
r1 = cq.Workplane(solid1).translate((10, 0, 0))
|
|
"""
|
|
|
|
def _modify_file(code, path="test.py"):
|
|
with open(path, "w", 1) as f:
|
|
f.write(code)
|
|
|
|
|
|
def modify_file(code, path="test.py"):
|
|
p = Process(target=_modify_file, args=(code,path))
|
|
p.start()
|
|
p.join()
|
|
|
|
def get_center(widget):
|
|
|
|
pos = widget.pos()
|
|
pos.setX(pos.x()+widget.width()//2)
|
|
pos.setY(pos.y()+widget.height()//2)
|
|
|
|
return pos
|
|
|
|
def get_bottom_left(widget):
|
|
|
|
pos = widget.pos()
|
|
pos.setY(pos.y()+widget.height())
|
|
|
|
return pos
|
|
|
|
def get_rgba(ais):
|
|
|
|
alpha = ais.Transparency()
|
|
color = get_occ_color(ais)
|
|
|
|
return color.redF(), color.greenF(), color.blueF(), alpha
|
|
|
|
@pytest.fixture
|
|
def main(qtbot,mocker):
|
|
|
|
mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.Yes)
|
|
|
|
win = MainWindow()
|
|
win.show()
|
|
|
|
qtbot.addWidget(win)
|
|
|
|
editor = win.components['editor']
|
|
editor.set_text(code)
|
|
|
|
debugger = win.components['debugger']
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
return qtbot, win
|
|
|
|
@pytest.fixture
|
|
def main_clean(qtbot,mocker):
|
|
|
|
mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.Yes)
|
|
|
|
win = MainWindow()
|
|
win.show()
|
|
|
|
qtbot.addWidget(win)
|
|
qtbot.waitForWindowShown(win)
|
|
|
|
editor = win.components['editor']
|
|
editor.set_text(code)
|
|
|
|
return qtbot, win
|
|
|
|
@pytest.fixture
|
|
def main_clean_do_not_close(qtbot,mocker):
|
|
|
|
mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.No)
|
|
|
|
win = MainWindow()
|
|
win.show()
|
|
|
|
qtbot.addWidget(win)
|
|
qtbot.waitForWindowShown(win)
|
|
|
|
editor = win.components['editor']
|
|
editor.set_text(code)
|
|
|
|
return qtbot, win
|
|
|
|
@pytest.fixture
|
|
def main_multi(qtbot,mocker):
|
|
|
|
mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.Yes)
|
|
mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.step',''))
|
|
|
|
win = MainWindow()
|
|
win.show()
|
|
|
|
qtbot.addWidget(win)
|
|
qtbot.waitForWindowShown(win)
|
|
|
|
editor = win.components['editor']
|
|
editor.set_text(code_multi)
|
|
|
|
debugger = win.components['debugger']
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
return qtbot, win
|
|
|
|
def test_render(main):
|
|
|
|
qtbot, win = main
|
|
|
|
obj_tree_comp = win.components['object_tree']
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
console = win.components['console']
|
|
log = win.components['log']
|
|
|
|
# enable CQ reloading
|
|
debugger.preferences['Reload CQ'] = True
|
|
|
|
# check that object was rendered
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
# check that object was removed
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# check that object was rendered usin explicit show_object call
|
|
editor.set_text(code_show_Workplane)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# check that cq.Shape object was rendered using explicit show_object call
|
|
editor.set_text(code_show_Shape)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# test rendering via console
|
|
console.execute(code_show_Workplane)
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
console.execute(code_show_Shape)
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
# check object rendering using show_object call with a name specified and
|
|
# debug call
|
|
editor.set_text(code_show_Workplane_named)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
qtbot.wait(100)
|
|
assert(obj_tree_comp.CQ.child(0).text(0) == 'test')
|
|
assert('test' in log.toPlainText().splitlines()[-1])
|
|
|
|
# cq reloading check
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
editor.set_text(code_reload_issue)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
qtbot.wait(100)
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
qtbot.wait(100)
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
def test_export(main,mocker):
|
|
|
|
qtbot, win = main
|
|
|
|
debugger = win.components['debugger']
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
#set focus
|
|
obj_tree = win.components['object_tree'].tree
|
|
obj_tree_comp = win.components['object_tree']
|
|
qtbot.mouseClick(obj_tree, Qt.LeftButton)
|
|
qtbot.keyClick(obj_tree, Qt.Key_Down)
|
|
qtbot.keyClick(obj_tree, Qt.Key_Down)
|
|
|
|
#export STL
|
|
mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.stl',''))
|
|
obj_tree_comp._export_STL_action.triggered.emit()
|
|
assert(os.path.isfile('out.stl'))
|
|
|
|
#export STEP
|
|
mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.step',''))
|
|
obj_tree_comp._export_STEP_action.triggered.emit()
|
|
assert(os.path.isfile('out.step'))
|
|
|
|
#clean
|
|
os.remove('out.step')
|
|
os.remove('out.stl')
|
|
|
|
def number_visible_items(viewer):
|
|
|
|
from OCP.AIS import AIS_ListOfInteractive
|
|
l = AIS_ListOfInteractive()
|
|
|
|
viewer_ctx = viewer._get_context()
|
|
viewer_ctx.DisplayedObjects(l)
|
|
|
|
return l.Extent()
|
|
|
|
def test_inspect(main):
|
|
|
|
qtbot, win = main
|
|
|
|
#set focus and make invisible
|
|
obj_tree = win.components['object_tree'].tree
|
|
qtbot.mouseClick(obj_tree, Qt.LeftButton)
|
|
qtbot.keyClick(obj_tree, Qt.Key_Down)
|
|
qtbot.keyClick(obj_tree, Qt.Key_Down)
|
|
qtbot.keyClick(obj_tree, Qt.Key_Space)
|
|
|
|
#enable object inspector
|
|
insp = win.components['cq_object_inspector']
|
|
insp._toolbar_actions[0].toggled.emit(True)
|
|
|
|
#check if all stack items are visible in the tree
|
|
assert(insp.root.childCount() == 3)
|
|
|
|
#check if correct number of items is displayed
|
|
viewer = win.components['viewer']
|
|
|
|
insp.setCurrentItem(insp.root.child(0))
|
|
assert(number_visible_items(viewer) == 4)
|
|
|
|
insp.setCurrentItem(insp.root.child(1))
|
|
assert(number_visible_items(viewer) == 7)
|
|
|
|
insp.setCurrentItem(insp.root.child(2))
|
|
assert(number_visible_items(viewer) == 4)
|
|
|
|
insp._toolbar_actions[0].toggled.emit(False)
|
|
assert(number_visible_items(viewer) == 3)
|
|
|
|
class event_loop(object):
|
|
'''Used to mock the QEventLoop for the debugger component
|
|
'''
|
|
|
|
def __init__(self,callbacks):
|
|
|
|
self.callbacks = callbacks
|
|
self.i = 0
|
|
|
|
def exec_(self):
|
|
|
|
if self.i<len(self.callbacks):
|
|
self.callbacks[self.i]()
|
|
self.i+=1
|
|
|
|
def exit(self,*args):
|
|
|
|
pass
|
|
|
|
def patch_debugger(debugger,event_loop_mock):
|
|
|
|
debugger.inner_event_loop.exec_ = event_loop_mock.exec_
|
|
debugger.inner_event_loop.exit = event_loop_mock.exit
|
|
|
|
def test_debug(main,mocker):
|
|
|
|
# store the tracing function
|
|
trace_function = sys.gettrace()
|
|
|
|
def assert_func(x):
|
|
'''Neddedd to perform asserts in lambdas
|
|
'''
|
|
assert(x)
|
|
|
|
qtbot, win = main
|
|
|
|
#clear all
|
|
obj_tree = win.components['object_tree']
|
|
obj_tree.toolbarActions()[0].triggered.emit()
|
|
|
|
editor = win.components['editor']
|
|
editor.set_text(code)
|
|
|
|
debugger = win.components['debugger']
|
|
actions = debugger._actions['Run']
|
|
run,debug,step,step_in,cont = actions
|
|
|
|
variables = win.components['variables_viewer']
|
|
|
|
viewer = win.components['viewer']
|
|
assert(number_visible_items(viewer) == 3)
|
|
|
|
#check breakpoints
|
|
assert(debugger.breakpoints == [])
|
|
|
|
#check _frames
|
|
assert(debugger._frames == [])
|
|
|
|
#test step through
|
|
ev = event_loop([lambda: (assert_func(variables.model().rowCount() == 4),
|
|
assert_func(number_visible_items(viewer) == 3),
|
|
step.triggered.emit()),
|
|
lambda: (assert_func(variables.model().rowCount() == 4),
|
|
assert_func(number_visible_items(viewer) == 3),
|
|
step.triggered.emit()),
|
|
lambda: (assert_func(variables.model().rowCount() == 5),
|
|
assert_func(number_visible_items(viewer) == 3),
|
|
step.triggered.emit()),
|
|
lambda: (assert_func(variables.model().rowCount() == 5),
|
|
assert_func(number_visible_items(viewer) == 4),
|
|
cont.triggered.emit())])
|
|
|
|
patch_debugger(debugger,ev)
|
|
|
|
debug.triggered.emit(True)
|
|
assert(variables.model().rowCount() == 2)
|
|
assert(number_visible_items(viewer) == 4)
|
|
|
|
#test exit debug
|
|
ev = event_loop([lambda: (step.triggered.emit(),),
|
|
lambda: (assert_func(variables.model().rowCount() == 1),
|
|
assert_func(number_visible_items(viewer) == 3),
|
|
debug.triggered.emit(False),)])
|
|
|
|
patch_debugger(debugger,ev)
|
|
|
|
debug.triggered.emit(True)
|
|
|
|
assert(variables.model().rowCount() == 1)
|
|
assert(number_visible_items(viewer) == 3)
|
|
|
|
#test breakpoint
|
|
ev = event_loop([lambda: (cont.triggered.emit(),),
|
|
lambda: (assert_func(variables.model().rowCount() == 5),
|
|
assert_func(number_visible_items(viewer) == 4),
|
|
cont.triggered.emit(),)])
|
|
|
|
patch_debugger(debugger,ev)
|
|
|
|
editor.debugger.set_breakpoints([(4,None)])
|
|
|
|
debug.triggered.emit(True)
|
|
|
|
assert(variables.model().rowCount() == 2)
|
|
assert(number_visible_items(viewer) == 4)
|
|
|
|
#test breakpoint without using singals
|
|
ev = event_loop([lambda: (cont.triggered.emit(),),
|
|
lambda: (assert_func(variables.model().rowCount() == 5),
|
|
assert_func(number_visible_items(viewer) == 4),
|
|
cont.triggered.emit(),)])
|
|
|
|
patch_debugger(debugger,ev)
|
|
|
|
editor.debugger.set_breakpoints([(4,None)])
|
|
|
|
debugger.debug(True)
|
|
|
|
assert(variables.model().rowCount() == 2)
|
|
assert(number_visible_items(viewer) == 4)
|
|
|
|
#test debug() without using singals
|
|
ev = event_loop([lambda: (cont.triggered.emit(),),
|
|
lambda: (assert_func(variables.model().rowCount() == 5),
|
|
assert_func(number_visible_items(viewer) == 4),
|
|
cont.triggered.emit(),)])
|
|
|
|
patch_debugger(debugger,ev)
|
|
|
|
editor.set_text(code_debug_Workplane)
|
|
editor.debugger.set_breakpoints([(4,None)])
|
|
|
|
debugger.debug(True)
|
|
|
|
CQ = obj_tree.CQ
|
|
|
|
# object 1 (defualt color)
|
|
r,g,b,a = get_rgba(CQ.child(0).ais)
|
|
assert( a == pytest.approx(0.2) )
|
|
assert( r == 1.0 )
|
|
|
|
assert(variables.model().rowCount() == 2)
|
|
assert(number_visible_items(viewer) == 4)
|
|
|
|
# restore the tracing function
|
|
sys.settrace(trace_function)
|
|
|
|
code_err1 = \
|
|
'''import cadquery as cq
|
|
(
|
|
result = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)
|
|
'''
|
|
|
|
code_err2 = \
|
|
'''import cadquery as cq
|
|
result = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)
|
|
f()
|
|
'''
|
|
|
|
def test_traceback(main):
|
|
|
|
# store the tracing function
|
|
trace_function = sys.gettrace()
|
|
|
|
qtbot, win = main
|
|
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
traceback_view = win.components['traceback_viewer']
|
|
|
|
actions = debugger._actions['Run']
|
|
run,debug,step,step_in,cont = actions
|
|
|
|
editor.set_text(code_err1)
|
|
run.triggered.emit()
|
|
|
|
assert('SyntaxError' in traceback_view.current_exception.text())
|
|
|
|
debug.triggered.emit()
|
|
|
|
assert('SyntaxError' in traceback_view.current_exception.text())
|
|
assert(debug.isChecked() == False)
|
|
|
|
editor.set_text(code_err2)
|
|
run.triggered.emit()
|
|
|
|
assert('NameError' in traceback_view.current_exception.text())
|
|
assert(hasattr(sys, 'last_traceback'))
|
|
|
|
del sys.last_traceback
|
|
assert(not hasattr(sys, 'last_traceback'))
|
|
|
|
|
|
#test last_traceback with debug
|
|
ev = event_loop([lambda: (cont.triggered.emit(),)])
|
|
patch_debugger(debugger,ev)
|
|
|
|
debugger.debug(True)
|
|
|
|
assert('NameError' in traceback_view.current_exception.text())
|
|
assert(hasattr(sys, 'last_traceback'))
|
|
|
|
# restore the tracing function
|
|
sys.settrace(trace_function)
|
|
|
|
@pytest.fixture
|
|
def editor(qtbot):
|
|
|
|
win = Editor()
|
|
win.show()
|
|
|
|
qtbot.addWidget(win)
|
|
|
|
return qtbot, win
|
|
|
|
def conv_line_ends(text):
|
|
|
|
return '\n'.join(text.splitlines())
|
|
|
|
def test_editor(monkeypatch,editor):
|
|
|
|
qtbot, editor = editor
|
|
|
|
with open('test.py','w') as f:
|
|
f.write(code)
|
|
|
|
#check that no text is present
|
|
assert(editor.get_text_with_eol() == '')
|
|
|
|
#check that loading from file works properly
|
|
editor.load_from_file('test.py')
|
|
assert(len(editor.get_text_with_eol()) > 0)
|
|
assert(conv_line_ends(editor.get_text_with_eol()) == code)
|
|
|
|
#check that loading from file works properly
|
|
editor.new()
|
|
assert(editor.get_text_with_eol() == '')
|
|
|
|
#monkeypatch QFileDialog methods
|
|
def filename(*args, **kwargs):
|
|
return 'test.py',None
|
|
|
|
def filename2(*args, **kwargs):
|
|
return 'test2.py',None
|
|
|
|
monkeypatch.setattr(QFileDialog, 'getOpenFileName',
|
|
staticmethod(filename))
|
|
|
|
monkeypatch.setattr(QFileDialog, 'getSaveFileName',
|
|
staticmethod(filename2))
|
|
|
|
#check that open file works properly
|
|
editor.open()
|
|
assert(conv_line_ends(editor.get_text_with_eol()) == code)
|
|
|
|
#check that save file works properly
|
|
editor.new()
|
|
qtbot.mouseClick(editor, Qt.LeftButton)
|
|
qtbot.keyClick(editor,Qt.Key_A)
|
|
|
|
assert(editor.document().isModified() == True)
|
|
|
|
editor.filename = 'test2.py'
|
|
editor.save()
|
|
|
|
assert(editor.document().isModified() == False)
|
|
|
|
monkeypatch.setattr(QFileDialog, 'getOpenFileName',
|
|
staticmethod(filename2))
|
|
|
|
editor.open()
|
|
assert(editor.get_text_with_eol() == 'a')
|
|
|
|
#check that save as works properly
|
|
os.remove('test2.py')
|
|
editor.save_as()
|
|
assert(os.path.exists(filename2()[0]))
|
|
|
|
#test persistance
|
|
settings = QSettings('test')
|
|
editor.saveComponentState(settings)
|
|
|
|
editor.new()
|
|
assert(editor.get_text_with_eol() == '')
|
|
|
|
editor.restoreComponentState(settings)
|
|
assert(editor.get_text_with_eol() == 'a')
|
|
|
|
#test error handling
|
|
os.remove('test2.py')
|
|
assert(not os.path.exists('test2.py'))
|
|
editor.restoreComponentState(settings)
|
|
|
|
@pytest.mark.repeat(1)
|
|
def test_editor_autoreload(monkeypatch,editor):
|
|
|
|
qtbot, editor = editor
|
|
|
|
TIMEOUT = 500
|
|
|
|
# start out with autoreload enabled
|
|
editor.autoreload(True)
|
|
|
|
with open('test.py','w') as f:
|
|
f.write(code)
|
|
|
|
assert(editor.get_text_with_eol() == '')
|
|
|
|
editor.load_from_file('test.py')
|
|
assert(len(editor.get_text_with_eol()) > 0)
|
|
|
|
# wait for reload.
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
# modify file - NB: separate process is needed to avoid Widows quirks
|
|
modify_file(code_bigger_object)
|
|
|
|
# check that editor has updated file contents
|
|
assert(code_bigger_object.splitlines()[2] in editor.get_text_with_eol())
|
|
|
|
# disable autoreload
|
|
editor.autoreload(False)
|
|
|
|
# Wait for reload in case it incorrectly happens. A timeout should occur
|
|
# instead because a re-render should not be triggered with autoreload
|
|
# disabled.
|
|
with pytest.raises(pytestqt.exceptions.TimeoutError):
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
# re-write original file contents
|
|
modify_file(code)
|
|
|
|
# editor should continue showing old contents since autoreload is disabled.
|
|
assert(code_bigger_object.splitlines()[2] in editor.get_text_with_eol())
|
|
|
|
# Saving a file with autoreload disabled should not trigger a rerender.
|
|
with pytest.raises(pytestqt.exceptions.TimeoutError):
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
editor.save()
|
|
|
|
editor.autoreload(True)
|
|
|
|
# Saving a file with autoreload enabled should trigger a rerender.
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
editor.save()
|
|
|
|
def test_autoreload_nested(editor):
|
|
|
|
qtbot, editor = editor
|
|
|
|
TIMEOUT = 500
|
|
|
|
editor.autoreload(True)
|
|
editor.preferences['Autoreload: watch imported modules'] = True
|
|
|
|
with open('test_nested_top.py','w') as f:
|
|
f.write(code_nested_top)
|
|
|
|
with open('test_nested_bottom.py','w') as f:
|
|
f.write("")
|
|
|
|
assert(editor.get_text_with_eol() == '')
|
|
|
|
editor.load_from_file('test_nested_top.py')
|
|
assert(len(editor.get_text_with_eol()) > 0)
|
|
|
|
# wait for reload.
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
# modify file - NB: separate process is needed to avoid Windows quirks
|
|
modify_file(code_nested_bottom, 'test_nested_bottom.py')
|
|
|
|
def test_console(main):
|
|
|
|
qtbot, win = main
|
|
|
|
console = win.components['console']
|
|
|
|
# test execute_command
|
|
a = []
|
|
console.push_vars({'a' : a})
|
|
console.execute_command('a.append(1)')
|
|
assert(len(a) == 1)
|
|
|
|
# test print_text
|
|
pos_orig = console._prompt_pos
|
|
console.print_text('a')
|
|
assert(console._prompt_pos == pos_orig + len('a'))
|
|
|
|
def test_viewer(main):
|
|
|
|
qtbot, win = main
|
|
|
|
viewer = win.components['viewer']
|
|
|
|
#not sure how to test this, so only smoke tests
|
|
|
|
#trigger all 'View' actions
|
|
actions = viewer._actions['View']
|
|
for a in actions: a.trigger()
|
|
|
|
code_module = \
|
|
'''def dummy(): return True'''
|
|
|
|
code_import = \
|
|
'''from module import dummy
|
|
assert(dummy())'''
|
|
|
|
def test_module_import(main):
|
|
|
|
qtbot, win = main
|
|
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
traceback_view = win.components['traceback_viewer']
|
|
|
|
#save the dummy module
|
|
with open('module.py','w') as f:
|
|
f.write(code_module)
|
|
|
|
#run the code importing this module
|
|
editor.set_text(code_import)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
#verify that no exception was generated
|
|
assert(traceback_view.current_exception.text() == '')
|
|
|
|
def test_auto_fit_view(main_clean):
|
|
|
|
def concat(eye,proj,scale):
|
|
return eye+proj+(scale,)
|
|
|
|
def approx_view_properties(eye,proj,scale):
|
|
|
|
return pytest.approx(eye+proj+(scale,))
|
|
|
|
qtbot, win = main_clean
|
|
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
viewer = win.components['viewer']
|
|
object_tree = win.components['object_tree']
|
|
|
|
view = viewer.canvas.view
|
|
viewer.preferences['Fit automatically'] = False
|
|
eye0,proj0,scale0 = view.Eye(),view.Proj(),view.Scale()
|
|
# check if camera position is adjusted automatically when rendering for the
|
|
# first time
|
|
debugger.render()
|
|
eye1,proj1,scale1 = view.Eye(),view.Proj(),view.Scale()
|
|
assert( concat(eye0,proj0,scale0) != \
|
|
approx_view_properties(eye1,proj1,scale1) )
|
|
|
|
# check if camera position is not changed fter code change
|
|
editor.set_text(code_bigger_object)
|
|
debugger.render()
|
|
eye2,proj2,scale2 = view.Eye(),view.Proj(),view.Scale()
|
|
assert( concat(eye1,proj1,scale1) == \
|
|
approx_view_properties(eye2,proj2,scale2) )
|
|
|
|
# check if position is adjusted automatically after erasing all objects
|
|
object_tree.removeObjects()
|
|
debugger.render()
|
|
eye3,proj3,scale3 = view.Eye(),view.Proj(),view.Scale()
|
|
assert( concat(eye2,proj2,scale2) != \
|
|
approx_view_properties(eye3,proj3,scale3) )
|
|
|
|
# check if position is adjusted automatically if settings are changed
|
|
viewer.preferences['Fit automatically'] = True
|
|
editor.set_text(code)
|
|
debugger.render()
|
|
eye4,proj4,scale4 = view.Eye(),view.Proj(),view.Scale()
|
|
assert( concat(eye3,proj3,scale3) != \
|
|
approx_view_properties(eye4,proj4,scale4) )
|
|
|
|
def test_preserve_properties(main):
|
|
qtbot, win = main
|
|
|
|
debugger = win.components['debugger']
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
object_tree = win.components['object_tree']
|
|
object_tree.preferences['Preserve properties on reload'] = True
|
|
|
|
assert(object_tree.CQ.childCount() == 1)
|
|
props = object_tree.CQ.child(0).properties
|
|
props['Visible'] = False
|
|
props['Color'] = '#caffee'
|
|
props['Alpha'] = 0.5
|
|
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
assert(object_tree.CQ.childCount() == 1)
|
|
props = object_tree.CQ.child(0).properties
|
|
assert(props['Visible'] == False)
|
|
assert(props['Color'].name() == '#caffee')
|
|
assert(props['Alpha'] == 0.5)
|
|
|
|
def test_selection(main_multi,mocker):
|
|
|
|
qtbot, win = main_multi
|
|
|
|
viewer = win.components['viewer']
|
|
object_tree = win.components['object_tree']
|
|
|
|
CQ = object_tree.CQ
|
|
obj1 = CQ.child(0)
|
|
obj2 = CQ.child(1)
|
|
|
|
# export with two selected objects
|
|
obj1.setSelected(True)
|
|
obj2.setSelected(True)
|
|
|
|
object_tree._export_STEP_action.triggered.emit()
|
|
imported = cq.importers.importStep('out.step')
|
|
assert(len(imported.solids().vals()) == 2)
|
|
|
|
# export with one selected objects
|
|
obj2.setSelected(False)
|
|
|
|
object_tree._export_STEP_action.triggered.emit()
|
|
imported = cq.importers.importStep('out.step')
|
|
assert(len(imported.solids().vals()) == 1)
|
|
|
|
# export with one selected objects
|
|
obj1.setSelected(False)
|
|
CQ.setSelected(True)
|
|
|
|
object_tree._export_STEP_action.triggered.emit()
|
|
imported = cq.importers.importStep('out.step')
|
|
assert(len(imported.solids().vals()) == 2)
|
|
|
|
# check if viewer and object tree are properly connected
|
|
CQ.setSelected(False)
|
|
obj1.setSelected(True)
|
|
obj2.setSelected(True)
|
|
ctx = viewer._get_context()
|
|
ctx.InitSelected()
|
|
shapes = []
|
|
|
|
while ctx.MoreSelected():
|
|
shapes.append(ctx.SelectedShape())
|
|
ctx.NextSelected()
|
|
assert(len(shapes) == 2)
|
|
|
|
viewer.fit()
|
|
qtbot.mouseClick(viewer.canvas, Qt.LeftButton)
|
|
|
|
assert(len(object_tree.tree.selectedItems()) == 0)
|
|
|
|
viewer.sigObjectSelected.emit([obj1.shape_display.wrapped])
|
|
assert(len(object_tree.tree.selectedItems()) == 1)
|
|
|
|
# go through different handleSelection paths
|
|
qtbot.mouseClick(object_tree.tree, Qt.LeftButton)
|
|
qtbot.keyClick(object_tree.tree, Qt.Key_Down)
|
|
qtbot.keyClick(object_tree.tree, Qt.Key_Down)
|
|
qtbot.keyClick(object_tree.tree, Qt.Key_Down)
|
|
qtbot.keyClick(object_tree.tree, Qt.Key_Down)
|
|
|
|
assert(object_tree._export_STL_action.isEnabled() == False)
|
|
assert(object_tree._export_STEP_action.isEnabled() == False)
|
|
assert(object_tree._clear_current_action.isEnabled() == False)
|
|
assert(object_tree.properties_editor.isEnabled() == False)
|
|
|
|
def test_closing(main_clean_do_not_close):
|
|
|
|
qtbot,win = main_clean_do_not_close
|
|
|
|
editor = win.components['editor']
|
|
|
|
# make sure that windows is visible
|
|
assert(win.isVisible())
|
|
|
|
# should not quit
|
|
win.close()
|
|
assert(win.isVisible())
|
|
|
|
# should quit
|
|
editor.reset_modified()
|
|
win.close()
|
|
assert(not win.isVisible())
|
|
|
|
def test_check_for_updates(main,mocker):
|
|
|
|
qtbot,win = main
|
|
|
|
# patch requests
|
|
import requests
|
|
mocker.patch.object(requests.models.Response,'json',
|
|
return_value=[{'tag_name' : '0.0.2','draft' : False}])
|
|
|
|
# stub QMessageBox about
|
|
about_stub = mocker.stub()
|
|
mocker.patch.object(QMessageBox, 'about', about_stub)
|
|
|
|
import cadquery
|
|
|
|
cadquery.__version__ = '0.0.1'
|
|
win.check_for_cq_updates()
|
|
assert(about_stub.call_args[0][1] == 'Updates available')
|
|
|
|
cadquery.__version__ = '0.0.3'
|
|
win.check_for_cq_updates()
|
|
assert(about_stub.call_args[0][1] == 'No updates available')
|
|
|
|
@pytest.mark.skipif(sys.platform.startswith('linux'),reason='Segfault workaround for linux')
|
|
def test_screenshot(main,mocker):
|
|
|
|
qtbot,win = main
|
|
|
|
mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.png',''))
|
|
|
|
viewer = win.components['viewer']
|
|
viewer._actions['Tools'][0].triggered.emit()
|
|
|
|
assert(os.path.exists('out.png'))
|
|
|
|
def test_resize(main):
|
|
|
|
qtbot,win = main
|
|
editor = win.components['editor']
|
|
|
|
editor.hide()
|
|
qtbot.wait(50)
|
|
editor.show()
|
|
qtbot.wait(50)
|
|
|
|
code_simple_step = \
|
|
'''import cadquery as cq
|
|
imported = cq.importers.importStep('shape.step')
|
|
'''
|
|
|
|
def test_relative_references(main):
|
|
|
|
# create code with a relative reference in a subdirectory
|
|
p = Path('test_relative_references')
|
|
p.mkdir_p()
|
|
p_code = p.joinpath('code.py')
|
|
p_code.write_text(code_simple_step)
|
|
# create the referenced step file
|
|
shape = cq.Workplane("XY").box(1, 1, 1)
|
|
p_step = p.joinpath('shape.step')
|
|
export(shape, "step", p_step)
|
|
# open code
|
|
qtbot, win = main
|
|
editor = win.components['editor']
|
|
editor.load_from_file(p_code)
|
|
# render
|
|
debugger = win.components['debugger']
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
# assert no errors
|
|
traceback_view = win.components['traceback_viewer']
|
|
assert(traceback_view.current_exception.text() == '')
|
|
# assert one object has been rendered
|
|
obj_tree_comp = win.components['object_tree']
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
# clean up
|
|
p_code.remove_p()
|
|
p_step.remove_p()
|
|
p.rmdir_p()
|
|
|
|
|
|
code_color = \
|
|
'''
|
|
import cadquery as cq
|
|
result = cq.Workplane("XY" ).box(1, 1, 1)
|
|
|
|
show_object(result, name ='1')
|
|
show_object(result, name ='2', options=dict(alpha=0.5,color='red'))
|
|
show_object(result, name ='3', options=dict(alpha=0.5,color='#ff0000'))
|
|
show_object(result, name ='4', options=dict(alpha=0.5,color=(255,0,0)))
|
|
show_object(result, name ='5', options=dict(alpha=0.5,color=(1.,0,0)))
|
|
show_object(result, name ='6', options=dict(rgba=(1.,0,0,.5)))
|
|
show_object(result, name ='7', options=dict(color=('ff','cc','dd')))
|
|
'''
|
|
|
|
def test_render_colors(main_clean):
|
|
|
|
qtbot, win = main_clean
|
|
|
|
obj_tree = win.components['object_tree']
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
log = win.components['log']
|
|
|
|
editor.set_text(code_color)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
|
|
CQ = obj_tree.CQ
|
|
|
|
# object 1 (defualt color)
|
|
assert not CQ.child(0).ais.HasColor()
|
|
|
|
# object 2
|
|
r,g,b,a = get_rgba(CQ.child(1).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
assert( g == 0.0 )
|
|
|
|
# object 3
|
|
r,g,b,a = get_rgba(CQ.child(2).ais)
|
|
assert( a == 0.5)
|
|
assert( r == 1.0 )
|
|
|
|
# object 4
|
|
r,g,b,a = get_rgba(CQ.child(3).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
|
|
# object 5
|
|
r,g,b,a = get_rgba(CQ.child(4).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
|
|
# object 6
|
|
r,g,b,a = get_rgba(CQ.child(5).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
|
|
# check if error occured
|
|
qtbot.wait(100)
|
|
assert('Unknown color format' in log.toPlainText().splitlines()[-1])
|
|
|
|
def test_render_colors_console(main_clean):
|
|
|
|
qtbot, win = main_clean
|
|
|
|
obj_tree = win.components['object_tree']
|
|
log = win.components['log']
|
|
console = win.components['console']
|
|
|
|
console.execute_command(code_color)
|
|
|
|
CQ = obj_tree.CQ
|
|
|
|
# object 1 (defualt color)
|
|
assert not CQ.child(0).ais.HasColor()
|
|
|
|
# object 2
|
|
r,g,b,a = get_rgba(CQ.child(1).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
|
|
# object 3
|
|
r,g,b,a = get_rgba(CQ.child(2).ais)
|
|
assert( a == 0.5)
|
|
assert( r == 1.0 )
|
|
|
|
# object 4
|
|
r,g,b,a = get_rgba(CQ.child(3).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
|
|
# object 5
|
|
r,g,b,a = get_rgba(CQ.child(4).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
|
|
# object 6
|
|
r,g,b,a = get_rgba(CQ.child(5).ais)
|
|
assert( a == 0.5 )
|
|
assert( r == 1.0 )
|
|
|
|
# check if error occured
|
|
qtbot.wait(100)
|
|
assert('Unknown color format' in log.toPlainText().splitlines()[-1])
|
|
|
|
def test_confirm_new(monkeypatch,editor):
|
|
|
|
qtbot, editor = editor
|
|
|
|
#check that initial state is as expected
|
|
assert(editor.modified == False)
|
|
|
|
editor.document().setPlainText(code)
|
|
assert(editor.modified == True)
|
|
|
|
#monkeypatch the confirmation dialog and run both scenarios
|
|
def cancel(*args, **kwargs):
|
|
return QMessageBox.No
|
|
|
|
def ok(*args, **kwargs):
|
|
return QMessageBox.Yes
|
|
|
|
monkeypatch.setattr(QMessageBox, 'question',
|
|
staticmethod(cancel))
|
|
|
|
editor.new()
|
|
assert(editor.modified == True)
|
|
assert(conv_line_ends(editor.get_text_with_eol()) == code)
|
|
|
|
monkeypatch.setattr(QMessageBox, 'question',
|
|
staticmethod(ok))
|
|
|
|
editor.new()
|
|
assert(editor.modified == False)
|
|
assert(editor.get_text_with_eol() == '')
|
|
|
|
code_show_topods = \
|
|
'''
|
|
import cadquery as cq
|
|
result = cq.Workplane("XY" ).box(1, 1, 1)
|
|
|
|
show_object(result.val().wrapped)
|
|
'''
|
|
|
|
def test_render_topods(main):
|
|
|
|
qtbot, win = main
|
|
|
|
obj_tree_comp = win.components['object_tree']
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
console = win.components['console']
|
|
|
|
# check that object was rendered
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
# check that object was removed
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# check that object was rendered usin explicit show_object call
|
|
editor.set_text(code_show_topods)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
# test rendering of topods object via console
|
|
console.execute('show(result.val().wrapped)')
|
|
assert(obj_tree_comp.CQ.childCount() == 2)
|
|
|
|
# test rendering of list of topods object via console
|
|
console.execute('show([result.val().wrapped,result.val().wrapped])')
|
|
assert(obj_tree_comp.CQ.childCount() == 3)
|
|
|
|
|
|
code_show_shape_list = \
|
|
'''
|
|
import cadquery as cq
|
|
result1 = cq.Workplane("XY" ).box(1, 1, 1).val()
|
|
result2 = cq.Workplane("XY",origin=(0,1,1)).box(1, 1, 1).val()
|
|
|
|
show_object(result1)
|
|
show_object([result1,result2])
|
|
'''
|
|
|
|
def test_render_shape_list(main):
|
|
|
|
qtbot, win = main
|
|
|
|
log = win.components['log']
|
|
|
|
obj_tree_comp = win.components['object_tree']
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
console = win.components['console']
|
|
|
|
# check that object was removed
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# check that object was rendered usin explicit show_object call
|
|
editor.set_text(code_show_shape_list)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 2)
|
|
|
|
# test rendering of Shape via console
|
|
console.execute('show(result1)')
|
|
console.execute('show([result1,result2])')
|
|
assert(obj_tree_comp.CQ.childCount() == 4)
|
|
|
|
# smoke test exception in show
|
|
console.execute('show("a")')
|
|
|
|
code_show_assy = \
|
|
'''import cadquery as cq
|
|
result1 = cq.Workplane("XY" ).box(3, 3, 0.5)
|
|
assy = cq.Assembly(result1)
|
|
|
|
show_object(assy)
|
|
'''
|
|
|
|
def test_render_assy(main):
|
|
|
|
qtbot, win = main
|
|
|
|
obj_tree_comp = win.components['object_tree']
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
console = win.components['console']
|
|
|
|
# check that object was removed
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# check that object was rendered usin explicit show_object call
|
|
editor.set_text(code_show_assy)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
qtbot.wait(500)
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
# test rendering via console
|
|
console.execute('show(assy)')
|
|
qtbot.wait(500)
|
|
assert(obj_tree_comp.CQ.childCount() == 2)
|
|
|
|
code_show_ais = \
|
|
'''import cadquery as cq
|
|
from cadquery.occ_impl.assembly import toCAF
|
|
|
|
import OCP
|
|
|
|
result1 = cq.Workplane("XY" ).box(3, 3, 0.5)
|
|
assy = cq.Assembly(result1)
|
|
|
|
lab, doc = toCAF(assy)
|
|
ais = OCP.XCAFPrs.XCAFPrs_AISObject(lab)
|
|
|
|
show_object(ais)
|
|
'''
|
|
|
|
def test_render_ais(main):
|
|
|
|
qtbot, win = main
|
|
|
|
obj_tree_comp = win.components['object_tree']
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
console = win.components['console']
|
|
|
|
# check that object was removed
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# check that object was rendered usin explicit show_object call
|
|
editor.set_text(code_show_ais)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
qtbot.wait(500)
|
|
assert(obj_tree_comp.CQ.childCount() == 1)
|
|
|
|
# test rendering via console
|
|
console.execute('show(ais)')
|
|
qtbot.wait(500)
|
|
assert(obj_tree_comp.CQ.childCount() == 2)
|
|
|
|
code_show_sketch = \
|
|
'''import cadquery as cq
|
|
|
|
s1 = cq.Sketch().rect(1,1)
|
|
s2 = cq.Sketch().segment((0,0), (0,3.),"s1")
|
|
|
|
show_object(s1)
|
|
show_object(s2)
|
|
'''
|
|
|
|
def test_render_sketch(main):
|
|
|
|
qtbot, win = main
|
|
|
|
obj_tree_comp = win.components['object_tree']
|
|
editor = win.components['editor']
|
|
debugger = win.components['debugger']
|
|
console = win.components['console']
|
|
|
|
# check that object was removed
|
|
obj_tree_comp._toolbar_actions[0].triggered.emit()
|
|
assert(obj_tree_comp.CQ.childCount() == 0)
|
|
|
|
# check that object was rendered usin explicit show_object call
|
|
editor.set_text(code_show_sketch)
|
|
debugger._actions['Run'][0].triggered.emit()
|
|
qtbot.wait(500)
|
|
assert(obj_tree_comp.CQ.childCount() == 2)
|
|
|
|
# test rendering via console
|
|
console.execute('show(s1); show(s2)')
|
|
qtbot.wait(500)
|
|
assert(obj_tree_comp.CQ.childCount() == 4)
|
|
|
|
def test_window_title(monkeypatch, main):
|
|
|
|
fname = 'test_window_title.py'
|
|
|
|
with open(fname, 'w') as f:
|
|
f.write(code)
|
|
|
|
qtbot, win = main
|
|
|
|
#monkeypatch QFileDialog methods
|
|
def filename(*args, **kwargs):
|
|
return fname, None
|
|
|
|
monkeypatch.setattr(QFileDialog, 'getOpenFileName',
|
|
staticmethod(filename))
|
|
|
|
win.components["editor"].open()
|
|
assert(win.windowTitle().endswith(fname))
|
|
|
|
# handle a new file
|
|
win.components["editor"].new()
|
|
# I don't really care what the title is, as long as it's not a filename
|
|
assert(not win.windowTitle().endswith('.py'))
|
|
|
|
def test_module_discovery(tmp_path, editor):
|
|
|
|
qtbot, editor = editor
|
|
with open(tmp_path.joinpath('main.py'), 'w') as f:
|
|
f.write('import b')
|
|
|
|
assert editor.get_imported_module_paths(str(tmp_path.joinpath('main.py'))) == []
|
|
|
|
tmp_path.joinpath('b.py').touch()
|
|
|
|
assert editor.get_imported_module_paths(str(tmp_path.joinpath('main.py'))) == [str(tmp_path.joinpath('b.py'))]
|
|
|
|
def test_launch_syntax_error(tmp_path):
|
|
|
|
# verify app launches when input file is bad
|
|
win = MainWindow()
|
|
|
|
inputfile = Path(tmp_path).joinpath("syntax_error.py")
|
|
modify_file("print(", inputfile)
|
|
editor = win.components["editor"]
|
|
editor.autoreload(True)
|
|
editor.preferences["Autoreload: watch imported modules"] = True
|
|
editor.load_from_file(inputfile)
|
|
|
|
win.show()
|
|
assert(win.isVisible())
|
|
|
|
code_import_module_makebox = \
|
|
"""
|
|
from module_makebox import *
|
|
z = 1
|
|
r = makebox(z)
|
|
"""
|
|
|
|
code_module_makebox = \
|
|
"""
|
|
import cadquery as cq
|
|
def makebox(z):
|
|
zval = z + 1
|
|
return cq.Workplane().box(1, 1, zval)
|
|
"""
|
|
|
|
def test_reload_import_handle_error(tmp_path, main):
|
|
|
|
TIMEOUT = 500
|
|
qtbot, win = main
|
|
editor = win.components["editor"]
|
|
debugger = win.components["debugger"]
|
|
traceback_view = win.components["traceback_viewer"]
|
|
|
|
editor.autoreload(True)
|
|
editor.preferences["Autoreload: watch imported modules"] = True
|
|
|
|
# save the module and top level script files
|
|
module_file = Path(tmp_path).joinpath("module_makebox.py")
|
|
script = Path(tmp_path).joinpath("main.py")
|
|
modify_file(code_module_makebox, module_file)
|
|
modify_file(code_import_module_makebox, script)
|
|
|
|
# run, verify that no exception was generated
|
|
editor.load_from_file(script)
|
|
debugger._actions["Run"][0].triggered.emit()
|
|
assert(traceback_view.current_exception.text() == "")
|
|
|
|
# save the module with an error
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
lines = code_module_makebox.splitlines()
|
|
lines.remove(" zval = z + 1") # introduce NameError
|
|
lines = "\n".join(lines)
|
|
modify_file(lines, module_file)
|
|
|
|
# verify NameError is generated
|
|
debugger._actions["Run"][0].triggered.emit()
|
|
assert("NameError" in traceback_view.current_exception.text())
|
|
|
|
# revert the error, verify rerender is triggered
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
modify_file(code_module_makebox, module_file)
|
|
|
|
# verify that no exception was generated
|
|
debugger._actions["Run"][0].triggered.emit()
|
|
assert(traceback_view.current_exception.text() == "")
|
|
|
|
def test_modulefinder(tmp_path, main):
|
|
|
|
TIMEOUT = 500
|
|
qtbot, win = main
|
|
editor = win.components["editor"]
|
|
debugger = win.components["debugger"]
|
|
traceback_view = win.components["traceback_viewer"]
|
|
log = win.components['log']
|
|
|
|
editor.autoreload(True)
|
|
editor.preferences["Autoreload: watch imported modules"] = True
|
|
|
|
script = Path(tmp_path).joinpath("main.py")
|
|
Path(tmp_path).joinpath("emptydir").mkdir()
|
|
modify_file("#import emptydir", script)
|
|
editor.load_from_file(script)
|
|
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
|
|
modify_file("import emptydir", script)
|
|
|
|
qtbot.wait(100)
|
|
assert("Cannot determine imported modules" in log.toPlainText().splitlines()[-1])
|
|
|