From 0e3e2e67bc1b5dbd7246495d6746da017fd0f004 Mon Sep 17 00:00:00 2001 From: janezd Date: Wed, 23 Jan 2019 21:50:47 +0100 Subject: [PATCH] OWSave: Add tests --- Orange/widgets/data/tests/test_owsave.py | 421 +++++++++++++++++------ 1 file changed, 309 insertions(+), 112 deletions(-) diff --git a/Orange/widgets/data/tests/test_owsave.py b/Orange/widgets/data/tests/test_owsave.py index d37a3239e28..0683fb8db37 100644 --- a/Orange/widgets/data/tests/test_owsave.py +++ b/Orange/widgets/data/tests/test_owsave.py @@ -1,145 +1,342 @@ -# Test methods with long descriptive names can omit docstrings -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, protected-access +import unittest from unittest.mock import patch, Mock -import itertools +import os + +from AnyQt.QtWidgets import QDialog + +import scipy.sparse as sp from Orange.data import Table -from Orange.data.io import Compression, CSVReader, TabReader, PickleReader, \ - ExcelReader, FileFormat +from Orange.data.io import CSVReader, TabReader, PickleReader, ExcelReader from Orange.tests import named_file from Orange.widgets.tests.base import WidgetTest -from Orange.widgets.utils.filedialogs import format_filter from Orange.widgets.data.owsave import OWSave -FILE_TYPES = [ - ("{} ({})".format(w.DESCRIPTION, w.EXTENSIONS[0]), - w.EXTENSIONS[0], - w.SUPPORT_SPARSE_DATA) - for w in (TabReader, CSVReader, PickleReader, ExcelReader) -] -COMPRESSIONS = [ - ("gzip ({})".format(Compression.GZIP), Compression.GZIP), - ("bzip2 ({})".format(Compression.BZIP2), Compression.BZIP2), - ("lzma ({})".format(Compression.XZ), Compression.XZ), -] +FILE_TYPES = [("{} ({})".format(w.DESCRIPTION, w.EXTENSIONS[0]), w) + for w in (TabReader, CSVReader, PickleReader, ExcelReader)] -class AddedFormat(FileFormat): - EXTENSIONS = ('.234',) - DESCRIPTION = "Test if a dialog format works after reading OWSave" +class TestOWSave(WidgetTest): + def setUp(self): + self.widget = self.create_widget(OWSave) # type: OWSave + self.iris = Table("iris") - def write_file(self): - pass + def test_dataset(self): + widget = self.widget + insum = widget.info.set_input_summary = Mock() + savefile = widget.save_file = Mock() + datasig = widget.Inputs.data + self.send_signal(datasig, self.iris) + self.assertEqual(insum.call_args[0][0], "150") + self.assertFalse(widget.bt_save.isEnabled()) + insum.reset_mock() + savefile.reset_mock() -class TestOWSave(WidgetTest): + widget.filename = "foo.tab" + widget.writer = TabReader + widget.auto_save = False + self.send_signal(datasig, self.iris) + self.assertEqual(insum.call_args[0][0], "150") + self.assertTrue(widget.bt_save.isEnabled()) + savefile.assert_not_called() - def setUp(self): - self.widget = self.create_widget(OWSave) # type: OWSave + widget.auto_save = True + self.send_signal(datasig, self.iris) + self.assertEqual(insum.call_args[0][0], "150") + self.assertTrue(widget.bt_save.isEnabled()) + savefile.assert_called() - def test_writer(self): - compressions = [self.widget.controls.compression.itemText(i) for i in - range(self.widget.controls.compression.count())] - types = [self.widget.controls.filetype.itemText(i) - for i in range(self.widget.controls.filetype.count())] - for t, c, d in itertools.product(types, compressions, [True, False]): - self.widget.compression = c - self.widget.compress = d - self.widget.filetype = t - self.widget.update_extension() - self.assertEqual( - len(self.widget.get_writer_selected().EXTENSIONS), 1) - - def test_ordinary_save(self): - self.send_signal(self.widget.Inputs.data, Table("iris")) - - for ext, suffix, _ in FILE_TYPES: - self.widget.filetype = ext - self.widget.update_extension() - writer = self.widget.get_writer_selected() - with named_file("", suffix=suffix) as filename: - def choose_file(a, b, c, d, e, fn=filename, w=writer): - return fn, format_filter(w) - - with patch("AnyQt.QtWidgets.QFileDialog.getSaveFileName", choose_file): - self.widget.save_file_as() - self.assertEqual(len(Table(filename)), 150) + self.send_signal(datasig, None) + insum.assert_called_with(widget.info.NoInput) + self.assertFalse(widget.bt_save.isEnabled()) + + @patch("Orange.widgets.data.owsave.QFileDialog") + def test_set_file_name_start_dir(self, filedialog): + widget = self.widget + + dlginst = filedialog.return_value + dlginst.exec.return_value = dlginst.Rejected = QDialog.Rejected + + widget.filename = "/usr/foo/bar.csv" + widget.filter = FILE_TYPES[1][0] + widget.set_file_name() + self.assertEqual(filedialog.call_args[0][2], widget.filename) + + widget.filename = "" + widget.last_dir = "/usr/bar" + widget.set_file_name() + self.assertEqual(filedialog.call_args[0][2], + os.path.join("/usr/bar", "")) # Yay, MS Windows! + + self.send_signal(widget.Inputs.data, self.iris) + widget.last_dir = "/usr/bar" + widget.set_file_name() + self.assertEqual(filedialog.call_args[0][2], "/usr/bar/iris.csv") + + widget.last_dir = "" + widget.set_file_name() + self.assertEqual(filedialog.call_args[0][2], + os.path.expanduser("~/iris.csv")) + + @patch("Orange.widgets.data.owsave.QFileDialog") + def test_set_file_name(self, filedialog): + widget = self.widget + widget.filename = "/usr/foo/bar.csv" + widget.last_dir = "/usr/foo/" + widget.writer = widget.writers[1] + widget.filter = FILE_TYPES[1][0] + + widget._update_controls = Mock() + + dlginst = filedialog.return_value + dlginst.selectedFiles.return_value = ["/bar/baz.csv"] + dlginst.selectedNameFilter.return_value = FILE_TYPES[0][0] + + dlginst.exec.return_value = dlginst.Rejected = QDialog.Rejected + widget.set_file_name() + self.assertEqual(widget.filename, "/usr/foo/bar.csv") + self.assertEqual(widget.last_dir, "/usr/foo/") + self.assertEqual(widget.filter, FILE_TYPES[1][0]) + self.assertIs(widget.writer, widget.writers[1]) + widget._update_controls.assert_not_called() + + dlginst.exec.return_value = dlginst.Accepted = QDialog.Accepted + widget.set_file_name() + self.assertEqual(widget.filename, "/bar/baz.csv") + self.assertEqual(widget.last_dir, "/bar") + self.assertEqual(widget.filter, FILE_TYPES[0][0]) + self.assertIs(widget.writer, widget.writers[0]) + widget._update_controls.assert_called() + + def set_mock_writer(self): + widget = self.widget + writer = widget.writer = Mock() + writer.write = Mock() + writer.SUPPORT_COMPRESSED = True + writer.SUPPORT_SPARSE_DATA = False + writer.OPTIONAL_TYPE_ANNOTATIONS = False + + def test_save_file_check_can_save(self): + widget = self.widget + self.set_mock_writer() + + widget.save_file() + widget.writer.write.assert_not_called() + + widget.filename = "foo" + widget.save_file() + widget.writer.write.assert_not_called() + + widget.filename = "" + self.send_signal(widget.Inputs.data, self.iris) + widget.save_file() + widget.writer.write.assert_not_called() + + widget.filename = "foo" + widget.save_file() + widget.writer.write.assert_called() + widget.writer.reset_mock() + + self.iris.X = sp.csr_matrix(self.iris.X) + widget.save_file() + widget.writer.write.assert_not_called() + + widget.writer.SUPPORT_SPARSE_DATA = True + widget.save_file() + widget.writer.write.assert_called() - @patch('Orange.data.io.FileFormat.write') - def test_annotations(self, write): + def test_save_file_write_errors(self): widget = self.widget + datasig = widget.Inputs.data - self.send_signal(widget.Inputs.data, Table("iris")) - widget.filetype = FILE_TYPES[1][0] - widget.filename = 'foo.csv' - widget.update_extension() + widget.auto_save = True + widget.filename = "bar/foo" + self.set_mock_writer() - widget.add_type_annotations = False - widget.unconditional_save_file() - write.assert_called() - self.assertFalse(write.call_args[0][2]) + widget.writer.write.side_effect = IOError + self.send_signal(datasig, self.iris) + self.assertTrue(widget.Error.general_error.is_shown()) + widget.writer.write.side_effect = None + self.send_signal(datasig, self.iris) + self.assertFalse(widget.Error.general_error.is_shown()) + + widget.writer.write.side_effect = IOError + self.send_signal(datasig, self.iris) + self.assertTrue(widget.Error.general_error.is_shown()) + + widget.writer.write.side_effect = None + self.send_signal(datasig, None) + self.assertFalse(widget.Error.general_error.is_shown()) + + widget.writer.write.side_effect = ValueError + self.assertRaises(ValueError, self.send_signal, datasig, self.iris) + + def test_save_file_write(self): + widget = self.widget + datasig = widget.Inputs.data + + self.set_mock_writer() + widget.auto_save = True + + widget.filename = "bar/foo.csv" + widget.compress = False widget.add_type_annotations = True - widget.unconditional_save_file() - self.assertTrue(write.call_args[0][2]) + self.send_signal(datasig, self.iris) + widget.writer.write.assert_called_with("bar/foo.csv", self.iris, True) + + widget.compress = True + self.send_signal(datasig, self.iris) + widget.writer.write.assert_called_with("bar/foo.csv.gz", + self.iris, True) - def test_disable_checkbox(self): + widget.writer.SUPPORT_COMPRESSED = False + self.send_signal(datasig, self.iris) + widget.writer.write.assert_called_with("bar/foo.csv", self.iris, True) + + def test_file_label(self): widget = self.widget - for type_ in FILE_TYPES: - widget.filetype = type_[0] - widget.update_extension() - if widget.get_writer_selected().OPTIONAL_TYPE_ANNOTATIONS: - self.assertTrue(widget.annotations_cb.isEnabled()) - else: - self.assertFalse(widget.annotations_cb.isEnabled()) - - def test_compression(self): - self.send_signal(self.widget.Inputs.data, Table("iris")) - - self.widget.compress = True - for type, compression in itertools.product([[x, ext] for x, ext, _ in FILE_TYPES], - COMPRESSIONS): - self.widget.filetype = type[0] - self.widget.compression = compression[0] - self.widget.update_extension() - writer = self.widget.get_writer_selected() - with named_file("", - suffix=type[1] + compression[1]) as filename: - def choose_file(a, b, c, d, e, fn=filename, w=writer): - return fn, format_filter(w) - - with patch("AnyQt.QtWidgets.QFileDialog.getSaveFileName", choose_file): - self.widget.save_file_as() - self.assertEqual(len(Table(filename)), 150) - def test_format_combo(self): + widget.filename = "" + widget._update_controls() + self.assertTrue(widget.lb_filename.isHidden()) + self.assertTrue(widget.Warning.no_file_name.is_shown()) + + widget.filename = "/foo/bar/baz.csv" + widget._update_controls() + self.assertFalse(widget.lb_filename.isHidden()) + self.assertIn(widget.lb_filename.text(), "Save to: /foo/bar/baz.csv") + self.assertFalse(widget.Warning.no_file_name.is_shown()) + + widget.filename = os.path.expanduser("~/baz/bar/foo.csv") + widget._update_controls() + self.assertFalse(widget.lb_filename.isHidden()) + self.assertEqual(widget.lb_filename.text(), "Save to: baz/bar/foo.csv") + + def test_annotation_checkbox(self): + widget = self.widget + for _, widget.writer in FILE_TYPES: + widget.filename = f"foo.{widget.writer.EXTENSIONS[0]}" + widget._update_controls() + self.assertIsNot(widget.controls.add_type_annotations.isHidden(), + widget.writer.OPTIONAL_TYPE_ANNOTATIONS, + msg=f"for {widget.writer}") + self.assertIsNot(widget.controls.compress.isHidden(), + widget.writer.SUPPORT_COMPRESSED, + msg=f"for {widget.writer}") + + widget.writer = TabReader + widget.filename = "" + self.widget._update_controls() + self.assertFalse(widget.controls.add_type_annotations.isVisible()) + self.assertFalse(widget.controls.compress.isVisible()) + + def test_sparse_error(self): widget = self.widget - filetype = widget.controls.filetype + err = widget.Error.unsupported_sparse - widget.save_file = Mock() + widget.writer = ExcelReader + widget.filename = "foo.xlsx" + widget.data = self.iris - data = Table("iris") - sparse_data = Table("iris") - sparse_data.is_sparse = Mock(return_value=True) + widget._update_controls() + self.assertFalse(err.is_shown()) - self.send_signal(widget.Inputs.data, data) - n_nonsparse = filetype.count() + widget.data.X = sp.csr_matrix(widget.data.X) + widget._update_controls() + self.assertTrue(err.is_shown()) - self.send_signal(widget.Inputs.data, sparse_data) - n_sparse = filetype.count() - self.assertGreater(n_nonsparse, n_sparse) + widget.writer = PickleReader + widget._update_controls() + self.assertFalse(err.is_shown()) + + widget.writer = ExcelReader + widget._update_controls() + self.assertTrue(err.is_shown()) + + widget.data = None + widget._update_controls() + self.assertFalse(err.is_shown()) + + def test_send_report(self): + widget = self.widget + + widget.report_items = Mock() + for _, writer in FILE_TYPES: + widget.writer = writer + for widget.compress in (False, True): + for widget.add_type_annotations in (False, True): + widget.filename = f"foo.{writer.EXTENSIONS[0]}" + widget.send_report() + items = dict(widget.report_items.call_args[0][0]) + msg = f"for {widget.writer}, " \ + f"annotations={widget.add_type_annotations}, " \ + f"compress={widget.compress}" + self.assertEqual(items["File name"], + f"foo.{writer.EXTENSIONS[0]}", msg=msg) + if writer.SUPPORT_COMPRESSED: + self.assertEqual( + items["Compression"], + ["No", "Yes"][widget.compress], + msg=msg) + else: + self.assertFalse(items["Compression"], msg=msg) + if writer.OPTIONAL_TYPE_ANNOTATIONS: + self.assertEqual( + items["Type annotations"], + ["No", "Yes"][widget.add_type_annotations], + msg=msg) + else: + self.assertFalse(items["Type annotations"], msg=msg) + + +class TestFunctionalOWSave(WidgetTest): + def setUp(self): + self.widget = self.create_widget(OWSave) # type: OWSave + self.iris = Table("iris") + + @patch("Orange.widgets.data.owsave.QFileDialog") + def test_save_uncompressed(self, filedialog): + widget = self.widget + widget.auto_save = False + + dlg = filedialog.return_value + dlg.exec.return_value = dlg.Accepted = QDialog.Accepted + dlg = filedialog.return_value + + spiris = Table("iris") + spiris.X = sp.csr_matrix(spiris.X) + + for dlg.selectedNameFilter.return_value, writer in FILE_TYPES: + widget.write = writer + ext = writer.EXTENSIONS[0] + with named_file("", suffix=ext) as filename: + dlg.selectedFiles.return_value = [filename] + + self.send_signal(widget.Inputs.data, self.iris) + widget.bt_set_file.click() + widget.bt_save.click() + self.assertEqual(len(Table(filename)), 150) - self.send_signal(widget.Inputs.data, sparse_data) - self.assertEqual(filetype.count(), n_sparse) + if writer.SUPPORT_SPARSE_DATA: + self.send_signal(widget.Inputs.data, spiris) + widget.bt_set_file.click() + widget.bt_save.click() + self.assertEqual(len(Table(filename)), 150) - self.send_signal(widget.Inputs.data, data) - self.assertEqual(filetype.count(), n_nonsparse) + if writer.SUPPORT_COMPRESSED: + with named_file("", suffix=ext + ".gz") as filename: + widget.compress = True + dlg.selectedFiles.return_value = [filename[:-3]] + self.send_signal(widget.Inputs.data, self.iris) + widget.bt_set_file.click() + widget.bt_save.click() + self.assertEqual(len(Table(filename)), 150) + widget.compress = False - self.send_signal(widget.Inputs.data, None) - self.send_signal(widget.Inputs.data, data) - self.assertEqual(filetype.count(), n_nonsparse) - self.send_signal(widget.Inputs.data, None) - self.send_signal(widget.Inputs.data, sparse_data) - self.assertEqual(filetype.count(), n_sparse) +if __name__ == "__main__": + unittest.main()