Skip to content

Commit

Permalink
Add first functional tests with "funq"
Browse files Browse the repository at this point in the history
  • Loading branch information
ubruhin committed Sep 23, 2018
1 parent 0ed1f04 commit 66828a3
Show file tree
Hide file tree
Showing 14 changed files with 602 additions and 0 deletions.
1 change: 1 addition & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ This directory contains tests for LibrePCB. Purpose of subdirectories:

- `data`: Data files (for example LibrePCB projects) used for the tests.
- `unittests`: Unit/integration tests for all static libraries of LibrePCB.
- `funq`: Functional tests (i.e. GUI tests) for LibrePCB.
- `cli`: System tests for the LibrePCB CLI.
3 changes: 3 additions & 0 deletions tests/funq/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest.ini
gitems.json
widgets_list.json
21 changes: 21 additions & 0 deletions tests/funq/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Functional Tests

This directory contains functional tests (simulating user input) using
[funq](https://github.com/parkouss/funq) and [pytest](https://docs.pytest.org).

## Create Virtualenv

mkvirtualenv -p `which python3` librepcb-funq

## Install Requirements

pip install -r requirements.txt

## Run Tests

pytest -v --librepcb-executable=/path/to/librepcb

## Links

- [Documentation of `funq`](http://funq.readthedocs.io/en/latest/)
- [Documentation of `pytest`](https://docs.pytest.org/en/latest/contents.html)
145 changes: 145 additions & 0 deletions tests/funq/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
################################################################################
# Control Panel
################################################################################

# Main window
controlPanel = librepcb__application__ControlPanel
controlPanelWidget = {controlPanel}::centralWidget

# Menu
controlPanelMenuBar = {controlPanel}::menuBar
controlPanelMenuExtras = {controlPanelMenuBar}::menuExtras

# Labels at top
controlPanelWarnForNoLibrariesLabel = {controlPanelWidget}::lblWarnForNoLibraries

# Buttons in the top right frame
controlPanelButtonsFrame = {controlPanelWidget}::splitter_h::layoutWidget1::frame
controlPanelNewProjectButton = {controlPanelButtonsFrame}::newProjectButton
controlPanelOpenProjectButton = {controlPanelButtonsFrame}::openProjectButton
controlPanelOpenLibraryManagerButton = {controlPanelButtonsFrame}::openLibraryManagerButton

# Open project dialog
controlPanelOpenProjectDialog = {controlPanel}::{QT_FILE_DIALOG}
controlPanelOpenProjectDialogFileNameEdit = {controlPanel}::{QT_FILE_DIALOG_LINE_EDIT}
controlPanelOpenProjectDialogOkButton = {controlPanel}::{QT_FILE_DIALOG_BTN_OK}

# New project wizard
controlPanelNewProjectWizard = {controlPanel}::librepcb__project__editor__NewProjectWizard
controlPanelNewProjectWizardWidget = {controlPanelNewProjectWizard}::QWidget
controlPanelNewProjectWizardNextButton = {controlPanelNewProjectWizardWidget}::__qt__passive_wizardbutton1
controlPanelNewProjectWizardFinishButton = {controlPanelNewProjectWizardWidget}::qt_wizard_finish
controlPanelNewProjectWizardFrame = {controlPanelNewProjectWizardWidget}::QFrame
controlPanelNewProjectWizardMetadataPage = {controlPanelNewProjectWizardFrame}::librepcb__project__editor__NewProjectWizardPage_Metadata
controlPanelNewProjectWizardMetadataNameEdit = {controlPanelNewProjectWizardMetadataPage}::edtName
controlPanelNewProjectWizardMetadataPathLabel = {controlPanelNewProjectWizardMetadataPage}::lblFullFilePath


################################################################################
# Library Manager
################################################################################

# Main window
libraryManager = {controlPanel}::librepcb__library__manager__LibraryManager
libraryManagerWidget = {libraryManager}::centralwidget

# Installed libraries list
libraryManagerInstalledLibrariesList = {libraryManagerWidget}::lstLibraries
libraryManagerInstalledLibrariesListViewport = {libraryManagerInstalledLibrariesList}::qt_scrollarea_viewport

# Add library widget
libraryManagerAddLibraryWidget = {libraryManagerWidget}::librepcb__library__manager____AddLibraryWidget
libraryManagerAddLibraryTabWidget = {libraryManagerAddLibraryWidget}::tabWidget
libraryManagerAddLibraryTabBar = {libraryManagerAddLibraryTabWidget}::qt_tabwidget_tabbar
libraryManagerAddLibraryStackedWidget = {libraryManagerAddLibraryTabWidget}::qt_tabwidget_stackedwidget

# Remote libraries tab
libraryManagerDownloadFromRepoTab = {libraryManagerAddLibraryStackedWidget}::tabDownloadFromRepo
libraryManagerDownloadFromRepoLibraryList = {libraryManagerDownloadFromRepoTab}::lstRepoLibs
libraryManagerDownloadFromRepoLibraryListViewport = {libraryManagerDownloadFromRepoLibraryList}::qt_scrollarea_viewport
libraryManagerDownloadFromRepoLibraryListItem = {libraryManagerDownloadFromRepoLibraryListViewport}::librepcb__library__manager__RepositoryLibraryListWidgetItem
libraryManagerDownloadFromRepoDownloadButton = {libraryManagerDownloadFromRepoTab}::btnRepoLibsDownload

# Create local library tab
libraryManagerCreateLocalLibraryTab = {libraryManagerAddLibraryStackedWidget}::tabCreateLocal
libraryManagerCreateLocalLibraryNameEdit = {libraryManagerCreateLocalLibraryTab}::edtLocalName
libraryManagerCreateLocalLibraryDescriptionEdit = {libraryManagerCreateLocalLibraryTab}::edtLocalDescription
libraryManagerCreateLocalLibraryAuthorEdit = {libraryManagerCreateLocalLibraryTab}::edtLocalAuthor
libraryManagerCreateLocalLibraryUrlEdit = {libraryManagerCreateLocalLibraryTab}::edtLocalUrl
libraryManagerCreateLocalLibraryVersionEdit = {libraryManagerCreateLocalLibraryTab}::edtLocalVersion
libraryManagerCreateLocalLibraryCc0LicenseCheckbox = {libraryManagerCreateLocalLibraryTab}::cbxLocalCc0License
libraryManagerCreateLocalLibraryDirectoryEdit = {libraryManagerCreateLocalLibraryTab}::edtLocalDirectory
libraryManagerCreateLocalLibraryCreateButton = {libraryManagerCreateLocalLibraryTab}::btnLocalCreate

# Library info widget
libraryManagerLibraryInfoWidget = {libraryManagerWidget}::librepcb__library__manager__LibraryInfoWidget
libraryManagerLibraryInfoWidgetOpenEditorButton = {libraryManagerLibraryInfoWidget}::btnOpenLibraryEditor


################################################################################
# Library Editor
################################################################################

# Main window
libraryEditor = librepcb__library__editor__LibraryEditor
libraryEditorWidget = {libraryEditor}::centralWidget

# Tab widget
libraryEditorTabWidget = {libraryEditorWidget}::tabWidget
libraryEditorStackedWidget = {libraryEditorTabWidget}::qt_tabwidget_stackedwidget

# Library overview widget
libraryEditorOverviewWidget = {libraryEditorStackedWidget}::librepcb__library__editor__LibraryOverviewWidget
libraryEditorOverviewMetadataWidget = {libraryEditorOverviewWidget}::splitter::layoutWidget
libraryEditorOverviewNameEdit = {libraryEditorOverviewMetadataWidget}::edtName
libraryEditorOverviewDescriptionEdit = {libraryEditorOverviewMetadataWidget}::edtDescription
libraryEditorOverviewKeywordsEdit = {libraryEditorOverviewMetadataWidget}::edtKeywords
libraryEditorOverviewAuthorEdit = {libraryEditorOverviewMetadataWidget}::edtAuthor
libraryEditorOverviewVersionEdit = {libraryEditorOverviewMetadataWidget}::edtVersion
libraryEditorOverviewDeprecatedCheckbox = {libraryEditorOverviewMetadataWidget}::cbxDeprecated
libraryEditorOverviewUrlEdit = {libraryEditorOverviewMetadataWidget}::edtUrl

# New element wizard
libraryEditorNewElementWizard = librepcb__library__editor__NewElementWizard
libraryEditorNewElementWizardWidget = {libraryEditorNewElementWizard}::centralWidget
libraryEditorNewElementWizardNextButton = {libraryEditorNewElementWizardWidget}::__qt__passive_wizardbutton1
libraryEditorNewElementWizardFinishButton = {libraryEditorNewElementWizardWidget}::qt_wizard_finish
libraryEditorNewElementWizardFrame = {libraryEditorNewElementWizard}::QFrame
libraryEditorNewElementWizardChooseTypePage = {libraryEditorNewElementWizardFrame}::librepcb__library__editor__NewElementWizardPage_ChooseType
libraryEditorNewElementWizardChooseTypeCmpCatButton = {libraryEditorNewElementWizardChooseTypePage}::btnComponentCategory
libraryEditorNewElementWizardChooseTypePkgCatButton = {libraryEditorNewElementWizardChooseTypePage}::btnPackageCategory
libraryEditorNewElementWizardChooseTypeSymbolButton = {libraryEditorNewElementWizardChooseTypePage}::btnSymbol
libraryEditorNewElementWizardChooseTypePackageButton = {libraryEditorNewElementWizardChooseTypePage}::btnPackage
libraryEditorNewElementWizardChooseTypeComponentButton = {libraryEditorNewElementWizardChooseTypePage}::btnComponent
libraryEditorNewElementWizardChooseTypeDeviceButton = {libraryEditorNewElementWizardChooseTypePage}::btnDevice
libraryEditorNewElementWizardMetadataPage = {libraryEditorNewElementWizardFrame}::librepcb__library__editor__NewElementWizardPage_EnterMetadata
libraryEditorNewElementWizardMetadataNameEdit = {libraryEditorNewElementWizardMetadataPage}::edtName
libraryEditorNewElementWizardMetadataDescriptionEdit = {libraryEditorNewElementWizardMetadataPage}::edtDescription
libraryEditorNewElementWizardMetadataKeywordsEdit = {libraryEditorNewElementWizardMetadataPage}::edtKeywords
libraryEditorNewElementWizardMetadataAuthorEdit = {libraryEditorNewElementWizardMetadataPage}::edtAuthor
libraryEditorNewElementWizardMetadataVersionEdit = {libraryEditorNewElementWizardMetadataPage}::edtVersion
libraryEditorNewElementWizardMetadataCategoryEdit = {libraryEditorNewElementWizardMetadataPage}::edtCategory
libraryEditorNewElementWizardMetadataCategoryLabel = {libraryEditorNewElementWizardMetadataPage}::lblCategoryTree
libraryEditorNewElementWizardMetadataCategoryButton = {libraryEditorNewElementWizardMetadataPage}::btnChooseCategory


################################################################################
# Schematic Editor
################################################################################

# Main window
schematicEditor = librepcb__project__editor__SchematicEditor
schematicEditorWidget = {schematicEditor}::centralWidget

# Graphics view
schematicEditorGraphicsView = {schematicEditor}::librepcb:_:GraphicsView
schematicEditorGraphicsViewWidget = {schematicEditorGraphicsView}::QWidget


################################################################################
# Board Editor
################################################################################

# Main window
boardEditor = librepcb__project__editor__BoardEditor
boardEditorWidget = {boardEditor}::centralWidget
163 changes: 163 additions & 0 deletions tests/funq/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import platform
import shutil
import pytest
import funq
from funq.client import ApplicationContext, ApplicationConfig


FUNQ_DIR = os.path.dirname(__file__)
TESTS_DIR = os.path.dirname(FUNQ_DIR)
REPO_DIR = os.path.dirname(TESTS_DIR)
DATA_DIR = os.path.join(TESTS_DIR, 'data')
FUNQ_DATA_DIR = os.path.join(DATA_DIR, 'funq')
FIXTURES_DATA_DIR = os.path.join(FUNQ_DATA_DIR, 'fixtures')


def pytest_addoption(parser):
parser.addoption("--librepcb-executable",
action="store",
help="Path to librepcb executable to test")


class GlobalOptions:
def __init__(self):
self.funq_conf = 'funq.conf'
self.funq_attach_exe = funq.tools.which('funq')
self.funq_gkit = 'default'
self.funq_gkit_file = os.path.join(os.path.dirname(os.path.realpath(funq.client.__file__)), 'aliases-gkits.conf')


class Application(object):
def __init__(self, executable, env=None, args=()):
super(Application, self).__init__()
cfg = ApplicationConfig(executable=executable, args=args, cwd=os.getcwd(), env=env,
aliases=os.path.join(FUNQ_DIR, 'aliases'), global_options=GlobalOptions())
self._context = ApplicationContext(cfg)

def __enter__(self):
return self._context.funq

def __exit__(self, exc_type, exc_val, exc_tb):
del self._context


class LibrePcbFixture(object):
def __init__(self, config, tmpdir):
super(LibrePcbFixture, self).__init__()
self.executable = os.path.abspath(config.getoption('--librepcb-executable'))
if not os.path.exists(self.executable):
raise Exception("Executable '{}' not found. Please pass it with "
"'--librepcb-executable'.".format(self.executable))
self.tmpdir = tmpdir
# Copy test data to temporary directory to avoid modifications in original data
self.data_dir = os.path.join(self.tmpdir, 'data')
shutil.copytree(FUNQ_DATA_DIR, self.data_dir)
# Init members to default values
self.workspace_path = os.path.join(self.data_dir, 'fixtures', 'Empty Workspace')
self.project_path = None

def abspath(self, relpath):
return os.path.join(self.tmpdir, relpath)

def set_workspace(self, path):
if not os.path.isabs(path):
path = self.abspath(path)
self.workspace_path = path

def set_project(self, path):
if not os.path.isabs(path):
path = self.abspath(path)
self.project_path = path

def add_local_library_to_workspace(self, path='data/fixtures/Empty Library.lplib'):
if not os.path.isabs(path):
path = self.abspath(path)
dest = os.path.join(self.workspace_path, 'v0.1', 'libraries', 'local')
dest = os.path.join(dest, os.path.basename(path))
shutil.copytree(path, dest)

def open(self):
self._create_application_config_file()
return Application(self.executable, env=self._env(), args=self._args())

def _create_application_config_file(self):
org_dir = 'LibrePCB.org' if platform.system() == 'Darwin' else 'LibrePCB'
config_dir = os.path.join(self.tmpdir, 'config', org_dir)
config_ini = os.path.join(config_dir, 'LibrePCB.ini')
if not os.path.exists(config_dir):
os.makedirs(config_dir)
with open(config_ini, 'w') as f:
if self.workspace_path:
f.write("[workspaces]\n")
f.write("most_recently_used=\"{}\"\n".format(self.workspace_path.replace('\\', '/')))

def _args(self):
args = []
if self.project_path:
args.append(self.project_path)
return args

def _env(self):
env = os.environ
# Make GUI independent from the system's language
env['LC_ALL'] = 'C'
# Override configuration location to make tests independent of existing configs
env['LIBREPCB_CONFIG_DIR'] = os.path.join(self.tmpdir, 'config')
# Use a neutral username
env['USERNAME'] = 'testuser'
# Force LibrePCB to use Qt-style file dialogs because native dialogs don't work
env['LIBREPCB_DISABLE_NATIVE_DIALOGS'] = '1'
return env


@pytest.fixture(scope="session")
def librepcb_server():
"""
Fixture which provides a HTTP server at localhost:8080
All tests should use this server instead of the official LibrePCB API server
or GitHub for downloading libraries.
"""
import time
import threading
import socket
import socketserver
import http.server

class Handler(http.server.SimpleHTTPRequestHandler, object):
def translate_path(self, path):
path = super(Handler, self).translate_path(path)
relpath = os.path.relpath(path, os.curdir)
return os.path.join(FIXTURES_DATA_DIR, 'server', relpath)

# Set SO_REUSEADDR option to avoid "port already in use" errors
httpd = socketserver.TCPServer(("", 50080), Handler, bind_and_activate=False)
httpd.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
httpd.server_bind()
httpd.server_activate()
thread = threading.Thread(target=httpd.serve_forever)
thread.daemon = True
thread.start()
time.sleep(0.2) # wait a bit to make sure the server is ready


@pytest.fixture
def create_librepcb(request, tmpdir, librepcb_server):
"""
Fixture allowing to create multiple application instances
"""
def _create():
return LibrePcbFixture(request.config, str(tmpdir))
return _create


@pytest.fixture
def librepcb(create_librepcb):
"""
Fixture allowing to create one application instance
"""
yield create_librepcb()
35 changes: 35 additions & 0 deletions tests/funq/controlpanel/test_create_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Test creating projects
"""

import os


def test_new_project_wizard(librepcb):
"""
Create project using the wizard from the control panel
"""
with librepcb.open() as app:
# Open new project wizard
app.widget('controlPanelNewProjectButton').click()
assert app.widget('controlPanelNewProjectWizard').properties()['visible'] is True
# Enter metadata
name = 'New Project'
app.widget('controlPanelNewProjectWizardMetadataNameEdit').set_property('text', name)
path = app.widget('controlPanelNewProjectWizardMetadataPathLabel').properties()['text']
app.widget('controlPanelNewProjectWizardNextButton').click()
# Setup schematic/board
app.widget('controlPanelNewProjectWizardFinishButton').click()
# Verify if editors are opened and project file exists
assert app.widget('schematicEditor').properties()['visible'] is True
assert app.widget('boardEditor').properties()['visible'] is True
assert os.path.exists(path)

# Open project again to see if it was saved properly
librepcb.set_project(path)
with librepcb.open() as app:
assert app.widget('schematicEditor').properties()['visible'] is True
assert app.widget('boardEditor').properties()['visible'] is True
Loading

0 comments on commit 66828a3

Please sign in to comment.