mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 00:46:01 +03:00
Merge branch 'master' into 2.1
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -61,5 +61,5 @@ updates
|
||||
.cache
|
||||
__pycache__
|
||||
|
||||
#Virtualenv
|
||||
# Virtualenv
|
||||
env
|
||||
|
||||
@@ -128,6 +128,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
self.uiInfoTreeWidget.addTopLevelItem(item)
|
||||
|
||||
elif self.page(page_id) == self.uiServerWizardPage:
|
||||
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
|
||||
is_win = ComputeManager.instance().localPlatform().startswith("win")
|
||||
|
||||
self.uiRemoteServersComboBox.clear()
|
||||
if len(ComputeManager.instance().remoteComputes()) == 0:
|
||||
self.uiRemoteRadioButton.setEnabled(False)
|
||||
@@ -141,7 +144,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
if ComputeManager.instance().localPlatform() is None:
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
elif (ComputeManager.instance().localPlatform().startswith("darwin") or ComputeManager.instance().localPlatform().startswith("win")):
|
||||
elif is_mac or is_win:
|
||||
if type == "qemu":
|
||||
# Qemu has issues on OSX and Windows we disallow usage of the local server
|
||||
if not LocalConfig.instance().experimental():
|
||||
@@ -158,6 +161,14 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
else:
|
||||
self.uiRemoteRadioButton.setChecked(False)
|
||||
|
||||
if is_mac or is_win:
|
||||
if not self.uiRemoteRadioButton.isEnabled() \
|
||||
and not self.uiVMRadioButton.isEnabled() \
|
||||
and not self.uiLocalRadioButton.isEnabled():
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, "No GNS3 VM available.",
|
||||
"GNS3 VM is not available, please configure GNS3 VM before adding new Appliance.")
|
||||
|
||||
elif self.page(page_id) == self.uiFilesWizardPage:
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
|
||||
|
||||
@@ -340,6 +340,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
"""
|
||||
Slot called to create a new project.
|
||||
"""
|
||||
|
||||
# prevents race condition
|
||||
if self._project_dialog is not None:
|
||||
return
|
||||
|
||||
self._project_dialog = ProjectDialog(self)
|
||||
self._project_dialog.show()
|
||||
create_new_project = self._project_dialog.exec_()
|
||||
@@ -348,7 +353,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiNodesDockWidget.setWindowTitle("")
|
||||
|
||||
if create_new_project:
|
||||
Topology.instance().createLoadProject(self._project_dialog.getProjectSettings())
|
||||
Topology.instance().createLoadProject(
|
||||
self._project_dialog.getProjectSettings())
|
||||
|
||||
self._project_dialog = None
|
||||
|
||||
@@ -727,7 +733,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
project.setShowLayers(self.uiShowLayersAction.isChecked())
|
||||
project.update()
|
||||
|
||||
|
||||
def _resetPortLabelsActionSlot(self):
|
||||
"""
|
||||
Slot called to reset the port labels on the scene.
|
||||
@@ -751,7 +756,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
project.setShowInterfaceLabels(self.uiShowPortNamesAction.isChecked())
|
||||
project.update()
|
||||
|
||||
|
||||
def _startAllActionSlot(self):
|
||||
"""
|
||||
Slot called when starting all the nodes.
|
||||
|
||||
@@ -256,9 +256,9 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
|
||||
QtWidgets.QMessageBox.critical(parent, "IOU image", "Cannot read ELF magic number: {}".format(e))
|
||||
return
|
||||
|
||||
# file must start with the ELF magic number, be 32-bit, little endian and have an ELF version of 1
|
||||
# normal IOS image are big endian!
|
||||
if elf_header_start != b'\x7fELF\x01\x01\x01':
|
||||
# file must start with the ELF magic number, be 32-bit or 64-bit, little endian and have an ELF version of 1
|
||||
# (normal IOS image are big endian!)
|
||||
if elf_header_start != b'\x7fELF\x01\x01\x01' and elf_header_start != b'\x7fELF\x02\x01\x01':
|
||||
QtWidgets.QMessageBox.critical(parent, "IOU image", "Sorry, this is not a valid IOU image!")
|
||||
return
|
||||
|
||||
|
||||
@@ -161,14 +161,12 @@ if hasattr(sys, '_called_from_test'):
|
||||
self._instances.add(self)
|
||||
|
||||
def connect(self, func, style=None):
|
||||
log.debug("{caller} connect to signal".format(caller=sys._getframe(1).f_code.co_name))
|
||||
self._callbacks.add(func)
|
||||
|
||||
def disconnect(self, func):
|
||||
self._callbacks.remove(func)
|
||||
|
||||
def emit(self, *args):
|
||||
log.debug("{caller} emit signal".format(caller=sys._getframe(1).f_code.co_name))
|
||||
for callback in list(self._callbacks):
|
||||
callback(*args)
|
||||
|
||||
|
||||
@@ -42,18 +42,20 @@ class QImageSvgRenderer(QtSvg.QSvgRenderer):
|
||||
|
||||
def load(self, path_or_data):
|
||||
try:
|
||||
if not os.path.exists(path_or_data) and not path_or_data.startswith(":"):
|
||||
self._svg = path_or_data
|
||||
path_or_data = path_or_data.encode("utf-8")
|
||||
return super().load(path_or_data)
|
||||
except ValueError:
|
||||
pass # On windows we can get an error because the path is too long (it's the svg data)
|
||||
path_exists = os.path.exists(path_or_data)
|
||||
except ValueError: # On windows we can get an error because the path is too long (it's the svg data)
|
||||
path_exists = False
|
||||
|
||||
if not path_exists and not path_or_data.startswith(":"):
|
||||
self._svg = path_or_data
|
||||
path_or_data = path_or_data.encode("utf-8")
|
||||
return super().load(path_or_data)
|
||||
|
||||
try:
|
||||
# We load the SVG with ElementTree before
|
||||
# because Qt when failing loading send noise to logs
|
||||
# and their is no way to prevent that
|
||||
if not path_or_data.startswith(":") and os.path.exists(path_or_data):
|
||||
if not path_or_data.startswith(":") and path_exists:
|
||||
ET.parse(path_or_data)
|
||||
res = super().load(path_or_data)
|
||||
# If we can't render a SVG we load and base64 the image to create a SVG
|
||||
|
||||
@@ -119,7 +119,7 @@ class Appliance(collections.Mapping):
|
||||
"""
|
||||
Duplicate a version in order to create a new version
|
||||
"""
|
||||
if len(self._appliance["versions"]) == 0:
|
||||
if 'versions' not in self._appliance.keys() or len(self._appliance["versions"]) == 0:
|
||||
raise ApplianceError("Your appliance file doesn't contain any versions")
|
||||
|
||||
ref = self._appliance["versions"][0]
|
||||
|
||||
@@ -54,7 +54,7 @@ class FileCopyWorker(QtCore.QObject):
|
||||
|
||||
self._is_running = True
|
||||
try:
|
||||
shutil.copyfile(self._source, self._destination)
|
||||
shutil.copy(self._source, self._destination)
|
||||
except OSError as e:
|
||||
log.warning("cannot copy: {}".format(e))
|
||||
self.error.emit("Could not copy file to {}: {}".format(self._destination, e), False)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import pytest
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from gns3.registry.appliance import Appliance, ApplianceError
|
||||
from gns3.registry.registry import Registry
|
||||
@@ -166,8 +167,11 @@ def test_is_version_installable(linux_microcore_img, microcore_appliance):
|
||||
|
||||
|
||||
def test_create_new_version():
|
||||
appliance_path = os.path.join(
|
||||
os.path.dirname(__file__), "appliances", "microcore-linux.gns3a")
|
||||
|
||||
a = Appliance(registry, appliance_path)
|
||||
|
||||
a = Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a"))
|
||||
a.create_new_version("42.0")
|
||||
v = a['versions'][-1:][0]
|
||||
assert v == {
|
||||
@@ -182,6 +186,21 @@ def test_create_new_version():
|
||||
'name': '42.0'
|
||||
}
|
||||
|
||||
# tests what happens without versions in file
|
||||
wrong_appliance_fp, wrong_appliance_file = tempfile.mkstemp()
|
||||
|
||||
with open(appliance_path) as f:
|
||||
appliance = json.loads(f.read())
|
||||
del appliance['versions']
|
||||
os.write(wrong_appliance_fp, json.dumps(appliance).encode())
|
||||
os.close(wrong_appliance_fp)
|
||||
|
||||
a = Appliance(registry, wrong_appliance_file)
|
||||
with pytest.raises(ApplianceError):
|
||||
a.create_new_version("42.0")
|
||||
|
||||
os.remove(wrong_appliance_file)
|
||||
|
||||
|
||||
def test_emulator():
|
||||
assert Appliance(registry, os.path.abspath("tests/registry/appliances/microcore-linux.gns3a")).emulator() == "qemu"
|
||||
|
||||
34
tests/test_main_window.py
Normal file
34
tests/test_main_window.py
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2017 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from unittest.mock import patch
|
||||
from gns3.main_window import MainWindow
|
||||
|
||||
|
||||
def test_new_project_when_dialog_is_already_available():
|
||||
main_window = MainWindow()
|
||||
with patch('gns3.main_window.ProjectDialog') as project_dialog_mock, \
|
||||
patch('gns3.topology.Topology.createLoadProject') as load_project_mock:
|
||||
|
||||
main_window._project_dialog = project_dialog_mock()
|
||||
|
||||
project_dialog_mock().exec_.return_value = 1
|
||||
project_dialog_mock().getProjectSettings.side_effect = AttributeError("Mocked")
|
||||
main_window._newProjectActionSlot()
|
||||
|
||||
# load is not called when we handle the case
|
||||
assert load_project_mock.called is False
|
||||
42
tests/utils/test_file_copy_worker.py
Normal file
42
tests/utils/test_file_copy_worker.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2017 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
from unittest.mock import patch
|
||||
|
||||
from gns3.utils.file_copy_worker import FileCopyWorker
|
||||
|
||||
|
||||
def test_file_copy_worker_with_preserve_permissions():
|
||||
source_fp, source = tempfile.mkstemp()
|
||||
destination_fp, destination = tempfile.mkstemp()
|
||||
|
||||
st = os.stat(source)
|
||||
os.chmod(source, st.st_mode | stat.S_IEXEC)
|
||||
assert os.access(source, os.X_OK)
|
||||
|
||||
with patch('gns3.utils.file_copy_worker.FileCopyWorker.finished'):
|
||||
worker = FileCopyWorker(source, destination)
|
||||
worker.run()
|
||||
assert os.access(destination, os.X_OK)
|
||||
|
||||
os.close(source_fp)
|
||||
os.close(destination_fp)
|
||||
os.remove(source)
|
||||
os.remove(destination)
|
||||
Reference in New Issue
Block a user