Repository: biolab/orange3

Issue:
TypeError:  OneClassSVMLearner doesn't support multiple class variables
https://sentry.io/biolab/orange3/issues/202498152/

```
TypeError:  OneClassSVMLearner doesn't support multiple class variables
  Module "Orange.widgets.data.owoutliers", line 141, in commit

    y_pred = self.detect_outliers()
  Module "Orange.widgets.data.owoutliers", line 169, in detect_outliers

    model = learner(self.data)
  Module "Orange.base", line 253, in __call__

    m = super().__call__(data)
  Module "Orange.base", line 52, in __call__

    self.__class__.__name__)
```


Discussion:

Resolving patch:
diff --git a/Orange/widgets/data/owoutliers.py b/Orange/widgets/data/owoutliers.py
--- a/Orange/widgets/data/owoutliers.py
+++ b/Orange/widgets/data/owoutliers.py
@@ -8,6 +8,7 @@
 from Orange.data import Table, Domain, ContinuousVariable
 from Orange.widgets import widget, gui
 from Orange.widgets.settings import Setting
+from Orange.widgets.widget import Msg
 from Orange.widgets.utils.sql import check_sql_input
 
 
@@ -36,6 +37,10 @@ class OWOutliers(widget.OWWidget):
     data_info_default = 'No data on input.'
     in_out_info_default = ' '
 
+    class Error(widget.OWWidget.Error):
+        singular_cov = Msg("Singular covariance matrix.")
+        multiclass_error = Msg("Multiple class data is not supported")
+
     def __init__(self):
         super().__init__()
         self.data = None
@@ -133,25 +138,29 @@ def set_data(self, dataset):
         self.commit()
 
     def commit(self):
-        self.error()
+        self.clear_messages()
         inliers = outliers = None
         self.n_inliers = self.n_outliers = None
         if self.data is not None and len(self.data) > 0:
-            try:
-                y_pred = self.detect_outliers()
-            except ValueError:
-                self.error("Singular covariance matrix.")
-                self.in_out_info_label.setText(self.in_out_info_default)
+            if self.data.Y.ndim > 1:
+                self.Error.multiclass_error()
             else:
-                inliers_ind = np.where(y_pred == 1)[0]
-                outliers_ind = np.where(y_pred == -1)[0]
-                inliers = Table(self.new_domain, self.new_data, inliers_ind)
-                outliers = Table(self.new_domain,
-                                 self.new_data, outliers_ind)
-                self.in_out_info_label.setText('%d inliers, %d outliers' %
-                                               (len(inliers), len(outliers)))
-                self.n_inliers = len(inliers)
-                self.n_outliers = len(outliers)
+                try:
+                    y_pred = self.detect_outliers()
+                except ValueError:
+                    self.Error.singular_cov()
+                    self.in_out_info_label.setText(self.in_out_info_default)
+                else:
+                    inliers_ind = np.where(y_pred == 1)[0]
+                    outliers_ind = np.where(y_pred == -1)[0]
+                    inliers = Table(self.new_domain, self.new_data, inliers_ind)
+                    outliers = Table(self.new_domain,
+                                     self.new_data, outliers_ind)
+                    self.in_out_info_label.setText(
+                        "{} inliers, {} outliers".format(len(inliers),
+                                                         len(outliers)))
+                    self.n_inliers = len(inliers)
+                    self.n_outliers = len(outliers)
 
         self.send("Inliers", inliers)
         self.send("Outliers", outliers)


Test patch:
diff --git a/Orange/widgets/data/tests/test_owoutliers.py b/Orange/widgets/data/tests/test_owoutliers.py
new file mode 100644
--- /dev/null
+++ b/Orange/widgets/data/tests/test_owoutliers.py
@@ -0,0 +1,38 @@
+# Test methods with long descriptive names can omit docstrings
+# pylint: disable=missing-docstring
+
+import numpy as np
+
+from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable
+from Orange.widgets.data.owoutliers import OWOutliers
+from Orange.widgets.tests.base import WidgetTest
+
+
+class TestOWOutliers(WidgetTest):
+    def setUp(self):
+        self.widget = self.create_widget(OWOutliers)
+        self.iris = Table("iris")
+
+    def test_data(self):
+        """Check widget's data and the output with data on the input"""
+        self.send_signal("Data", self.iris)
+        self.assertEqual(self.widget.data, self.iris)
+        self.assertEqual(len(self.get_output("Inliers")), 76)
+        self.assertEqual(len(self.get_output("Outliers")), 74)
+        self.send_signal("Data", None)
+        self.assertEqual(self.widget.data, None)
+        self.assertIsNone(self.get_output("Data"))
+
+    def test_multiclass(self):
+        """Check widget for multiclass dataset"""
+        attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")]
+        class_vars = [DiscreteVariable("cls", ["a", "b", "c"]),
+                      DiscreteVariable("cls", ["aa", "bb", "cc"])]
+        domain = Domain(attrs, class_vars)
+        X = np.arange(12).reshape(6, 2)
+        Y = np.array([[0, 1], [2, 1], [0, 2], [1, 1], [1, 2], [2, 0]])
+        data = Table(domain, X, Y)
+        self.send_signal("Data", data)
+        self.assertTrue(self.widget.Error.multiclass_error.is_shown())
+        self.send_signal("Data", None)
+        self.assertFalse(self.widget.Error.multiclass_error.is_shown())