UnitTest: Tensor-src-tizensensor / Initial test cases
authorMyungJoo Ham <myungjoo.ham@samsung.com>
Tue, 26 Nov 2019 09:42:06 +0000 (18:42 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Fri, 13 Dec 2019 04:07:53 +0000 (13:07 +0900)
1. Unit tests are added.
2. dummy_sensor.c/h offers dummy Tizen sensor framework for unit tests,
where we cannot activate Tizen sensor framework daemon.
3. Bugs were found with the unit tests and they are fixed.
    - g_hash_table misuses
    - property configuration errors
    - frequency (framerate) handling
    - Clean up and configured-status handling
    - Mutex deadlock
    - Timestamp handling

Changes v1->v2:
- Use G_USEC_PER_SEC for readability, suggested by dongju.chae@samsung.com
Changes v2->v3:
- Added error-clear in testcase, suggested by dongju.chae@samsung.com
- Removed unnecessary code (residue of internal tests), suggested by jy1210.jung@samsung.com
Changes v3->v4:
- Added free ops for pipelines, removed duplicated codes, suggested by jy1210.jung@samsung.com

Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
api/capi/src/nnstreamer-capi-pipeline.c
ext/nnstreamer/tensor_source/tensor_src_tizensensor.c
packaging/nnstreamer.spec
tests/tizen_capi/dummy_sensor.c [new file with mode: 0644]
tests/tizen_capi/dummy_sensor.h [new file with mode: 0644]
tests/tizen_capi/meson.build
tests/tizen_capi/sensor.h [new file with mode: 0644]
tests/tizen_capi/unittest_tizen_sensor.cpp [new file with mode: 0644]

index b21f3c3..e71d048 100644 (file)
@@ -197,6 +197,10 @@ cb_sink_event (GstElement * e, GstBuffer * b, gpointer user_data)
           for (i = 0; i < elem->tensors_info.num_tensors; i++) {
             size_t sz = ml_tensor_info_get_size (&elem->tensors_info.info[i]);
 
+            /* Not configured, yet. */
+            if (sz == 0)
+              ml_loge ("The caps for sink(%s) is not configured.", elem->name);
+
             if (sz != data->tensors[i].size) {
               ml_loge
                   ("The sink event of [%s] cannot be handled because the tensor dimension mismatches.",
index d9c8ee7..3da560b 100644 (file)
@@ -44,7 +44,7 @@
  * Tizen-API / sensor_get_sensor_list(ACCELEROMETER, list, count);
  * When the sequence is not specified, the first (.0) is chosen.
  * You may specify the enum value of the sensor (sensor_type_e) defined
- * in sensor.h of Tizen with type, instead of typename.
+ * in sensor.h of Tizen with type.
  *
  * If sequence = -1 (default), we use "default sensor".
  *
@@ -55,6 +55,8 @@
  *
  * @todo Add "Listener" mode (creates data only if there are updates)
  *
+ * @todo Every mode should handle timestamp/duration properly!
+ *
  * @todo Add "power management" options (Tizen sensor f/w accepts such)
  *
  * @todo Some sensor tpes are privileged. We need privilege control.
@@ -141,8 +143,8 @@ enum
  */
 #define DEFAULT_PROP_SEQUENCE -1
 
-#define _LOCK(obj) g_mutex_lock (&(obj)->lock);
-#define _UNLOCK(obj) g_mutex_unlock (&(obj)->lock);
+#define _LOCK(obj) g_mutex_lock (&(obj)->lock)
+#define _UNLOCK(obj) g_mutex_unlock (&(obj)->lock)
 
 /**
  * @brief Template for src pad.
@@ -380,7 +382,7 @@ gst_tensor_src_tizensensor_class_init (GstTensorSrcTIZENSENSORClass * klass)
           "Produce verbose output", DEFAULT_PROP_SILENT,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   g_object_class_install_property (gobject_class, PROP_TYPE,
-      g_param_spec_enum ("typename", "Tizen Sensor Type Name (enum)",
+      g_param_spec_enum ("type", "Tizen Sensor Type (enum)",
           "Tizen sensor type as a enum-name, defined in sensor.h of Tizen",
           GST_TYPE_TIZEN_SENSOR_TYPE, SENSOR_ALL,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
@@ -394,7 +396,7 @@ gst_tensor_src_tizensensor_class_init (GstTensorSrcTIZENSENSORClass * klass)
           GST_TYPE_TIZEN_SENSOR_MODE, TZN_SENSOR_MODE_POLLING,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   g_object_class_install_property (gobject_class, PROP_FREQ,
-      gst_param_spec_fraction ("freq", "Frequency",
+      gst_param_spec_fraction ("framerate", "Framerate",
           "Rate of data retrievals from a sensor. Effective only when "
           "mode is ACTIVE_POLLING",
           0, 1, G_MAXINT, 1,
@@ -458,11 +460,11 @@ gst_tensor_src_tizensensor_init (GstTensorSrcTIZENSENSOR * self)
 
   if (NULL == tizensensors) {
     int i;
-    tizensensors = g_hash_table_new (g_int_hash, g_int_equal);
+    tizensensors = g_hash_table_new (g_direct_hash, g_direct_equal);
 
     for (i = 0; tizensensorspecs[i].type != SENSOR_LAST; i++) {
       g_assert (g_hash_table_insert (tizensensors,
-              (gpointer) tizensensorspecs[i].type,
+              GINT_TO_POINTER (tizensensorspecs[i].type),
               &tizensensorspecs[i].tinfo) == TRUE);
       g_assert (tizensensorspecs[i].value_count ==
           tizensensorspecs[i].tinfo.dimension[0]);
@@ -532,6 +534,9 @@ static void __attribute__ ((unused))
 static unsigned int
 _ts_get_interval_ms (GstTensorSrcTIZENSENSOR * self)
 {
+  if (self->freq_n == 0)
+    return 100;                 /* If it's 0Hz, assume 100ms interval */
+
   g_assert (self->freq_d > 0 && self->freq_n > 0);
 
   return gst_util_uint64_scale_int ((guint64) self->freq_d, 1000, self->freq_n);
@@ -545,9 +550,11 @@ _ts_configure_handle (GstTensorSrcTIZENSENSOR * self)
 {
   int ret = 0;
   const GstTensorInfo *val = g_hash_table_lookup (tizensensors,
-      (gpointer) self->type);
+      GINT_TO_POINTER (self->type));
   bool supported = false;
 
+  if (NULL == val)
+    g_printerr ("The given sensor type (%d) is not supported.\n", self->type);
   g_assert (val);
   self->src_spec = val;
 
@@ -582,7 +589,8 @@ _ts_configure_handle (GstTensorSrcTIZENSENSOR * self)
       GST_ERROR_OBJECT (self,
           "The requested sensor sequence %d for sensor %d is not available. The max-sequence is used instead",
           self->sequence, self->type);
-      self->sequence = count - 1;
+      self->sequence = 0;
+      return -EINVAL;
     }
 
     self->sensor = list[self->sequence];
@@ -656,7 +664,8 @@ gst_tensor_src_tizensensor_set_property (GObject * object,
 
       if (new_type != self->type) {
         /* Different sensor is being used. Clean it up! */
-        ret = _ts_clean_up_handle (self);
+        if (self->configured)
+          ret = _ts_clean_up_handle (self);
 
         if (ret) {
           GST_ERROR_OBJECT (self, "_ts_clean_up_handle() returns %d", ret);
@@ -679,7 +688,8 @@ gst_tensor_src_tizensensor_set_property (GObject * object,
 
       if (self->sequence != new_sequence) {
         /* Different sensor is being used. Clean it up! */
-        ret = _ts_clean_up_handle (self);
+        if (self->configured)
+          ret = _ts_clean_up_handle (self);
 
         if (ret) {
           GST_ERROR_OBJECT (self, "_ts_clean_up_handle() returns %d", ret);
@@ -733,6 +743,11 @@ gst_tensor_src_tizensensor_set_property (GObject * object,
       self->freq_n = gst_value_get_fraction_numerator (value);
       self->freq_d = gst_value_get_fraction_denominator (value);
 
+      if (self->freq_n < 0)
+        self->freq_n = 0;
+      if (self->freq_d < 1)
+        self->freq_d = 1;
+
       silent_debug ("Set operating frequency %d/%d --> %d/%d",
           n, d, self->freq_n, self->freq_d);
 
@@ -781,6 +796,10 @@ gst_tensor_src_tizensensor_get_property (GObject * object,
       g_value_set_enum (value, self->mode);
       break;
     case PROP_FREQ:
+      if (self->freq_d < 1)
+        self->freq_d = 1;
+      if (self->freq_n < 0)
+        self->freq_n = 0;
       gst_value_set_fraction (value, self->freq_n, self->freq_d);
       break;
     default:
@@ -838,6 +857,10 @@ gst_tensor_src_tizensensor_start (GstBaseSrc * src)
 
   /* 2. Configure handle / context */
   ret = _ts_configure_handle (self);
+  if (ret) {
+    retval = FALSE;
+    goto exit;
+  }
   g_assert (self->configured == TRUE);
 
   /** @todo TBD. Let's assume each frame has a fixed size */
@@ -901,17 +924,18 @@ gst_tensor_src_tizensensor_event (GstBaseSrc * src, GstEvent * event)
 static GstCaps *
 _ts_get_gstcaps_from_conf (GstTensorSrcTIZENSENSOR * self)
 {
-  TizenSensorSpec *spec;
+  const GstTensorInfo *spec;
   gchar *tensor;
   GstCaps *retval;
 
-  spec = g_hash_table_lookup (tizensensors, (gpointer) self->type);
+  /** @bug the retrieved spec is not valid...? */
+  spec = g_hash_table_lookup (tizensensors, GINT_TO_POINTER (self->type));
 
   if (FALSE == self->configured || SENSOR_ALL == self->type || NULL == spec) {
     tensor = g_strdup_printf ("other/tensor; other/tensors, num_tensors=1");
   } else {
-    const gchar *typestr = gst_tensor_get_type_string (spec->tinfo.type);
-    gchar *dimstr = gst_tensor_get_dimension_string (spec->tinfo.dimension);
+    const gchar *typestr = gst_tensor_get_type_string (spec->type);
+    gchar *dimstr = gst_tensor_get_dimension_string (spec->dimension);
     g_assert (typestr && typestr[0]);
     tensor = g_strdup_printf ("other/tensor, dimension=%s, type=%s ; "
         "other/tensors, num_tensors=1, dimensions=%s, type=%s",
@@ -1068,7 +1092,9 @@ gst_tensor_src_tizensensor_create (GstBaseSrc * src, guint64 offset,
   }
   gst_buffer_append_memory (buf, mem);
 
+  _UNLOCK (self);
   retval = gst_tensor_src_tizensensor_fill (src, offset, buffer_size, buf);
+  _LOCK (self);
   if (retval != GST_FLOW_OK)
     goto exit;
 
@@ -1132,6 +1158,15 @@ gst_tensor_src_tizensensor_fill (GstBaseSrc * src, guint64 offset,
   GstMemory *mem;
   GstMapInfo map;
 
+  if (self->freq_n > 0) {
+    gulong usec;
+    g_assert (self->freq_d > 0);
+
+    usec = ((gulong) self->freq_d * G_USEC_PER_SEC) / self->freq_n;
+    /** @tood Substract latency of itself */
+    g_usleep (usec);
+  }
+
   _LOCK (self);
 
   if (FALSE == self->configured) {
@@ -1214,10 +1249,9 @@ gst_tensor_src_tizensensor_fill (GstBaseSrc * src, guint64 offset,
         pts -= diff;
       GST_BUFFER_PTS (buffer) = pts;
     } else {
-      GST_ERROR_OBJECT (self,
-          "The given buffer for fill function does not have a valid timestamp.");
-      retval = GST_FLOW_ERROR;
-      goto exit_unmap;
+      /* There is no valid timestamp. Override the timestamp */
+
+      /** @todo NYI. Need research on this matter */
     }
     GST_BUFFER_PTS (buffer) = event.timestamp;
 
index ff99c65..93b42e3 100644 (file)
@@ -300,6 +300,10 @@ ninja -C build %{?_smp_mflags}
 %if 0%{?enable_nnfw_r}
     ./tests/tizen_nnfw_runtime/unittest_nnfw_runtime_raw --gst-plugin-path=. --gtest_output="xml:unittest_nnfw_runtime_raw.xml"
 %endif
+%if %{with tizen}
+    ln -s ext/nnstreamer/tensor_source/*.so .
+    ./tests/tizen_capi/unittest_tizen_sensor --gst-plugin-path=. --gtest_output="xml:unittest_tizen_sensor.xml"
+%endif
     popd
     pushd tests
     ssat -n
diff --git a/tests/tizen_capi/dummy_sensor.c b/tests/tizen_capi/dummy_sensor.c
new file mode 100644 (file)
index 0000000..c4cca62
--- /dev/null
@@ -0,0 +1,259 @@
+/**
+ * @file       dummy_sensor.h
+ * @date       28 Nov 2019
+ * @brief      Dummy Tizen Sensor API support for unit tests.
+ * @see                https://github.com/nnsuite/nnstreamer
+ * @author      MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug         No known bugs
+ * @details    The sensor framework source plugin should be
+ *              linked with dummy_sensor.
+ */
+
+#include <time.h>
+
+#include "dummy_sensor.h"
+#include <errno.h>
+#include <string.h>
+
+static void init_timestamps (void) __attribute__ ((constructor));
+
+static sensor_s sensors[][3] = {
+  /* 0 = SENSOR_ACCELEROMETER */
+  {{.type = SENSOR_ACCELEROMETER,.id = 0,.listeners = NULL, .last_recorded =
+              {0}},
+      {.type = SENSOR_ACCELEROMETER,.id = 1,.listeners = NULL, .last_recorded =
+            {0}},
+      {.type = SENSOR_ACCELEROMETER,.id = 2,.listeners = NULL, .last_recorded =
+            {0}}},
+  {{0}, {0}, {0}},                          /* 1 */
+  {{0}, {0}, {0}},                          /* 1 */
+  {{0}, {0}, {0}},                          /* 2 */
+  {{0}, {0}, {0}},                          /* 3 */
+  {{0}, {0}, {0}},                          /* 4 */
+  {{0}, {0}, {0}},                          /* 5 */
+  {{0}, {0}, {0}},                          /* 6 */
+  {{.type = SENSOR_LIGHT,.id = 0,.listeners = NULL, .last_recorded = {0}},
+      {.type = SENSOR_LIGHT,.id = 1,.listeners = NULL, .last_recorded = {0}},
+      {.type = SENSOR_LIGHT,.id = 2,.listeners = NULL, .last_recorded = {0}}}
+};
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_is_supported (sensor_type_e type, bool * supported)
+{
+  if (type == SENSOR_ACCELEROMETER || type == SENSOR_LIGHT)
+    *supported = true;
+  else
+    *supported = false;
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_get_default_sensor (sensor_type_e type, sensor_h * sensor)
+{
+  bool supported;
+
+  sensor_is_supported (type, &supported);
+  if (supported == false) {
+    return -EINVAL;
+  }
+
+  *sensor = &(sensors[type][0]);
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_get_sensor_list (sensor_type_e type, sensor_h ** list, int *sensor_count)
+{
+  bool supported;
+  int i;
+
+  sensor_is_supported (type, &supported);
+  if (supported == false) {
+    *list = NULL;
+    *sensor_count = 0;
+    return 0;
+  }
+
+  *list = g_new0 (sensor_h, 3);
+  for (i = 0; i < 3; i++)
+    (*list)[i] = &(sensors[type][i]);
+  *sensor_count = 3;
+
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_get_type (sensor_h sensor, sensor_type_e * type)
+{
+  sensor_s *ptr = sensor;
+  bool supported;
+
+  sensor_is_supported (ptr->type, &supported);
+  if (supported)
+    *type = ptr->type;
+  else
+    return -EINVAL;
+
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_create_listener (sensor_h sensor, sensor_listener_h * listener)
+{
+  sensor_listener_s *ptr = g_new0 (sensor_listener_s, 1);
+  sensor_type_e type;
+  GHashTable *table;
+
+  ptr->is_listening = 0;
+  ptr->listening = sensor;
+
+  if (NULL == sensor || sensor_get_type (sensor, &type) < 0 ||
+      ptr->listening->id > 3)
+    return -EINVAL;
+
+  if (NULL == ptr->listening->listeners) {
+    ptr->listening->listeners = g_hash_table_new (NULL, NULL);
+  }
+  table = ptr->listening->listeners;
+
+  g_hash_table_add (table, ptr);
+
+  *listener = ptr;
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_destroy_listener (sensor_listener_h listener)
+{
+  bool removed = false;
+  sensor_listener_s *l = listener;
+  sensor_s *s;
+  GHashTable *table;
+
+  if (l == NULL)
+    return -EINVAL;
+
+  s = l->listening;
+  if (s == NULL)
+    return -EINVAL;
+
+  table = s->listeners;
+  if (table == NULL)
+    return -EINVAL;
+
+  removed = g_hash_table_remove (table, l);
+  if (removed == false)
+    return -EINVAL;
+
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_listener_start (sensor_listener_h listener)
+{
+  sensor_listener_s *ptr = listener;
+  if (NULL == listener)
+    return -EINVAL;
+
+  ptr->is_listening = 1;
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_listener_stop (sensor_listener_h listener)
+{
+  sensor_listener_s *ptr = listener;
+  if (NULL == listener)
+    return -EINVAL;
+
+  ptr->is_listening = 0;
+  return 0;
+}
+
+/**
+ * @brief Dummy (simulation) Tizen Sensor Framework API
+ */
+int
+sensor_listener_read_data (sensor_listener_h listener, sensor_event_s * event)
+{
+  sensor_listener_s *ptr = listener;
+  sensor_s *s;
+
+  if (NULL == listener || NULL == event)
+    return -EINVAL;
+
+  s = ptr->listening;
+  if (NULL == s || !ptr->is_listening)
+    return -EINVAL;
+
+  memcpy (event, &(s->last_recorded), sizeof (sensor_event_s));
+
+  return 0;
+}
+
+/**
+ * @brief Dummy Tizen Sensor.
+ */
+int
+dummy_publish (sensor_h sensor, sensor_event_s value)
+{
+  sensor_s *s;
+
+  if (NULL == sensor)
+    return -EINVAL;
+
+  s = sensor;
+
+  memcpy (&(s->last_recorded), &value, sizeof (sensor_event_s));
+
+  if (s->last_recorded.timestamp == 0) {
+    struct timespec t;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    s->last_recorded.timestamp = ((unsigned long long)(t.tv_sec)*1000000LL +
+        (unsigned long long)(t.tv_nsec)/1000);
+  }
+
+  return 0;
+}
+
+/**
+ * @brief Initialize default timestamps values to avoid runtime errors in Gst
+ */
+static void init_timestamps (void)
+{
+  struct timespec t;
+  unsigned long long ts;
+  clock_gettime(CLOCK_MONOTONIC, &t);
+  ts = ((unsigned long long)(t.tv_sec)*1000000LL +
+      (unsigned long long)(t.tv_nsec)/1000);
+  sensors[0][0].last_recorded.timestamp = ts;
+  sensors[0][1].last_recorded.timestamp = ts;
+  sensors[0][2].last_recorded.timestamp = ts;
+  sensors[7][0].last_recorded.timestamp = ts;
+  sensors[7][1].last_recorded.timestamp = ts;
+  sensors[7][2].last_recorded.timestamp = ts;
+}
diff --git a/tests/tizen_capi/dummy_sensor.h b/tests/tizen_capi/dummy_sensor.h
new file mode 100644 (file)
index 0000000..9b9b04d
--- /dev/null
@@ -0,0 +1,163 @@
+/**
+ * @file       dummy_sensor.h
+ * @date       28 Nov 2019
+ * @brief      Dummy Tizen Sensor API support for unit tests.
+ * @see                https://github.com/nnsuite/nnstreamer
+ * @author      MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug         No known bugs
+ * @details    The sensor framework source plugin should be
+ *              linked with dummy_sensor.
+ *
+ *              This will simply connect values from publish()
+ *              to listener().
+ *
+ *              This has sensor-fw APIs that are used by
+ *              nnstreamer only.
+ */
+#ifndef __DUMMY_SENSOR_H__
+#define __DUMMY_SENSOR_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#include <stdint.h>
+#include <stdbool.h>
+#include <glib.h>
+
+#include <errno.h>
+#include <tizen_error.h>
+
+
+typedef enum
+{
+       SENSOR_ALL = -1,                        /**< All sensors. This can be used to retrieve #sensor_h for all available sensors. */
+       SENSOR_ACCELEROMETER,                   /**< Accelerometer */
+       SENSOR_GRAVITY,                         /**< Gravity sensor */
+       SENSOR_LINEAR_ACCELERATION,             /**< Linear acceleration sensor */
+       SENSOR_MAGNETIC,                        /**< Magnetic sensor */
+       SENSOR_ROTATION_VECTOR,                 /**< Rotation vector sensor */
+       SENSOR_ORIENTATION,                     /**< Orientation sensor */
+       SENSOR_GYROSCOPE,                       /**< Gyroscope */
+       SENSOR_LIGHT,                           /**< Light sensor */
+       SENSOR_PROXIMITY,                       /**< Proximity sensor */
+       SENSOR_PRESSURE,                        /**< Pressure sensor */
+       SENSOR_ULTRAVIOLET,                     /**< Ultraviolet sensor */
+       SENSOR_TEMPERATURE,                     /**< Temperature sensor */
+       SENSOR_HUMIDITY,                        /**< Humidity sensor */
+       SENSOR_HRM,                             /**< Heart-rate monitor @if MOBILE (Since 2.3.1) @endif
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_HRM_LED_GREEN,                   /**< Green LED sensor of HRM @if MOBILE (Since 2.3.1) @endif
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_HRM_LED_IR,                      /**< Infra-Red LED sensor of HRM @if MOBILE (Since 2.3.1) @endif
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_HRM_LED_RED,                     /**< Red LED sensor of HRM @if MOBILE (Since 2.3.1) @endif
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_GYROSCOPE_UNCALIBRATED,          /**< Uncalibrated Gyroscope sensor
+                                                    @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+       SENSOR_GEOMAGNETIC_UNCALIBRATED,        /**< Uncalibrated Geomagnetic sensor
+                                                    @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+       SENSOR_GYROSCOPE_ROTATION_VECTOR,       /**< Gyroscope-based rotation vector sensor
+                                                    @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+       SENSOR_GEOMAGNETIC_ROTATION_VECTOR,     /**< Geomagnetic-based rotation vector sensor
+                                                    @if MOBILE (Since 2.4) @elseif WEARABLE (Since 2.3.2) @endif */
+       SENSOR_SIGNIFICANT_MOTION = 0x100,      /**< Significant motion sensor (Since 4.0) */
+       SENSOR_HUMAN_PEDOMETER = 0x300,         /**< Pedometer (Since 3.0)
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_HUMAN_SLEEP_MONITOR,             /**< Sleep monitor (Since 3.0)
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_HUMAN_SLEEP_DETECTOR,            /**< Sleep detector (Since 3.0)
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_HUMAN_STRESS_MONITOR,            /**< Stress monitor (Since 3.0)
+                                                    @n Privilege : http://tizen.org/privilege/healthinfo */
+       SENSOR_LAST,                            /**< End of sensor enum values (Deprecated since 3.0) */
+       SENSOR_CUSTOM = 0x2710,                 /**< Custom sensor (Deprecated since 3.0) */
+} sensor_type_e;
+
+/**
+ * @brief   Enumeration for errors.
+ * @since_tizen @if MOBILE 2.3 @elseif WEARABLE 2.3.1 @endif
+ */
+typedef enum {
+       SENSOR_ERROR_NONE                  = TIZEN_ERROR_NONE,                 /**< Successful */
+       SENSOR_ERROR_IO_ERROR              = TIZEN_ERROR_IO_ERROR,             /**< I/O error */
+       SENSOR_ERROR_INVALID_PARAMETER     = TIZEN_ERROR_INVALID_PARAMETER,    /**< Invalid parameter */
+       SENSOR_ERROR_NOT_SUPPORTED         = TIZEN_ERROR_NOT_SUPPORTED,        /**< Not supported */
+       SENSOR_ERROR_PERMISSION_DENIED     = TIZEN_ERROR_PERMISSION_DENIED,    /**< Permission denied */
+       SENSOR_ERROR_OUT_OF_MEMORY         = TIZEN_ERROR_OUT_OF_MEMORY,        /**< Out of memory */
+       SENSOR_ERROR_NO_DATA               = TIZEN_ERROR_NO_DATA,              /**< No data available
+                                                                                @if MOBILE (Since 3.0) @elseif WEARABLE (Since 2.3.2) @endif */
+       SENSOR_ERROR_NOT_NEED_CALIBRATION  = TIZEN_ERROR_SENSOR | 0x03,        /**< Sensor doesn't need calibration */
+       SENSOR_ERROR_OPERATION_FAILED      = TIZEN_ERROR_SENSOR | 0x06,        /**< Operation failed */
+       SENSOR_ERROR_NOT_AVAILABLE         = TIZEN_ERROR_SENSOR | 0x07,        /**< The sensor is supported, but currently not available
+                                                                                @if MOBILE (Since 3.0) @elseif WEARABLE (Since 2.3.2) @endif */
+} sensor_error_e;
+
+
+/* event should be exactly same with the original */
+typedef struct
+{
+  int accuracy;                  /**< Accuracy of sensor data */
+  unsigned long long timestamp;  /**< Time when the sensor data was observed */
+  int value_count;               /**< Number of sensor data values stored in #sensor_event_s::values */
+  float values[16];  /**< Sensor data values */
+} sensor_event_s;
+
+typedef struct {
+  sensor_type_e type;
+  uint32_t id;
+  GHashTable *listeners;
+  sensor_event_s last_recorded;
+} sensor_s;
+typedef void* sensor_h;
+
+typedef struct {
+  sensor_s *listening;
+  int is_listening;
+} sensor_listener_s;
+
+typedef void* sensor_listener_h;
+
+
+
+
+
+/* main */
+extern int
+sensor_is_supported (sensor_type_e type, bool * supported);
+
+extern int
+sensor_get_default_sensor (sensor_type_e type, sensor_h *sensor);
+
+extern int
+sensor_get_sensor_list (sensor_type_e type, sensor_h **list, int *sensor_count);
+
+extern int
+sensor_get_type (sensor_h sensor, sensor_type_e *type);
+
+
+
+/* listener */
+extern int
+sensor_create_listener (sensor_h sensor, sensor_listener_h *listener);
+
+extern int
+sensor_destroy_listener (sensor_listener_h listener);
+
+extern int
+sensor_listener_start (sensor_listener_h listener);
+
+extern int
+sensor_listener_stop (sensor_listener_h listener);
+
+extern int
+sensor_listener_read_data (sensor_listener_h listener, sensor_event_s *event);
+
+
+/* publish data */
+extern int
+dummy_publish (sensor_h sensor, sensor_event_s value);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* __DUMMY_SENSOR_H__ */
index 73529ef..74c6711 100644 (file)
@@ -11,3 +11,32 @@ unittest_tizen_capi = executable('unittest_tizen_capi',
   install_dir: unittest_install_dir
 )
 test('unittest_tizen_capi', unittest_tizen_capi, args: ['--gst-plugin-path=../..'])
+
+
+if get_option('enable-tizen-sensor')
+
+  unittest_sensor_helper = shared_library('capi-system-sensor',
+    ['dummy_sensor.c'],
+    dependencies: [glib_dep],
+  )
+  unittest_sensor_helper_dep = declare_dependency(
+    link_with: unittest_sensor_helper,
+  )
+
+  tizen_sensor_apptest_deps = [
+    gtest_dep,
+    glib_dep,
+    gst_dep,
+    gst_base_dep,
+    nnstreamer_dep,
+    nnstreamer_capi_dep,
+    unittest_sensor_helper_dep,
+  ]
+  unittest_tizen_sensor = executable('unittest_tizen_sensor',
+    ['unittest_tizen_sensor.cpp'],
+    dependencies: [tizen_sensor_apptest_deps],
+    install: get_option('install-test'),
+    install_dir: unittest_install_dir
+  )
+  test('unittest_tizen_sensor', unittest_tizen_sensor, args: ['--gst-plugin-path=../..'])
+endif
diff --git a/tests/tizen_capi/sensor.h b/tests/tizen_capi/sensor.h
new file mode 100644 (file)
index 0000000..157e3b3
--- /dev/null
@@ -0,0 +1,9 @@
+/**
+ * @file       sensor.h
+ * @date       28 Nov 2019
+ * @brief      Tizen Sensor Framework Simulator
+ * @see                https://github.com/nnsuite/nnstreamer
+ * @author      MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug         No known bugs
+ */
+#include "dummy_sensor.h"
diff --git a/tests/tizen_capi/unittest_tizen_sensor.cpp b/tests/tizen_capi/unittest_tizen_sensor.cpp
new file mode 100644 (file)
index 0000000..62afb91
--- /dev/null
@@ -0,0 +1,451 @@
+/**
+ * @file       unittest_tizen_sensor.cpp
+ * @date       25 Nov 2019
+ * @brief      Unit test for NNStreamer's tensor-src-tizensensor.
+ * @see                https://github.com/nnsuite/nnstreamer
+ * @author      MyungJoo Ham <myungjoo.ham@samsung.com>
+ * @bug         No known bugs
+ */
+
+#ifndef __TIZEN__
+/* These works only in Tizen */
+#error This unit test works only in Tizen. This needs Tizen Sensor Framework.
+#endif
+
+#include <glib.h>
+#include "dummy_sensor.h" /* Dummy Tizen Sensor Framework */
+#include <gtest/gtest.h>
+#include <gst/gst.h>
+#include <nnstreamer.h>
+#include <nnstreamer-capi-private.h>
+
+/**
+ * @brief Test pipeline creation of it
+ */
+TEST (tizensensor_as_source, virtual_sensor_create_01)
+{
+  gchar *pipeline;
+  sensor_event_s value;
+  sensor_h sensor;
+  GstElement *gstpipe;
+  GError *err = NULL;
+  int status = 0;
+
+  sensor_get_default_sensor (SENSOR_LIGHT, &sensor);
+
+  value.accuracy = 1;
+  value.timestamp = 0U;
+  value.value_count = 1;
+  value.values[0] = 0.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  /* Create a nnstreamer pipeline */
+  pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=0 num-buffers=3 ! fakesink");
+  gstpipe = gst_parse_launch (pipeline, &err);
+  if (gstpipe) {
+    status = 0;
+    gst_object_unref (gstpipe);
+  } else {
+    status = -1;
+    g_printerr("GST PARSE LAUNCH FAILED: [%s], %s\n",
+      pipeline, (err) ? err->message : "unknown reason");
+    g_clear_error (&err);
+  }
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_free (pipeline);
+}
+
+/**
+ * @brief Test pipeline creation.
+ */
+TEST (tizensensor_as_source, virtual_sensor_create_02)
+{
+  gchar *pipeline;
+  sensor_event_s value;
+  sensor_h sensor;
+  sensor_h *sensor_list;
+  GstElement *gstpipe;
+  int status = 0;
+  int count;
+
+  sensor_get_sensor_list (SENSOR_LIGHT, &sensor_list, &count);
+  EXPECT_EQ (count, 3);
+  sensor = sensor_list[2];
+  g_free (sensor_list);
+
+  value.accuracy = 1;
+  value.timestamp = 0U;
+  value.value_count = 1;
+  value.values[0] = 0.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  /* Create a nnstreamer pipeline */
+  pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_ACCELEROMETER sequence=-1 num-buffers=3 ! fakesink");
+  gstpipe = gst_parse_launch (pipeline, NULL);
+  if (gstpipe) {
+    status = 0;
+    gst_object_unref (gstpipe);
+  } else {
+    status = -1;
+  }
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_free (pipeline);
+
+}
+
+#define MAX_VERIFY_DATA (256)
+typedef struct {
+  int cursor;
+  int num_data;
+  float golden[256][16];
+  ml_tensor_type_e type;
+  int dim0;
+  int checked;
+  int negative;
+} verify_data;
+
+/**
+ * @brief Test if the sensor-reading matches the golden values.
+ */
+static void callback_nns (const ml_tensors_data_h data,
+    const ml_tensors_info_h info, void *user_data)
+{
+  verify_data *vdata = (verify_data *) user_data;
+  int status;
+  unsigned int count;
+  ml_tensor_type_e type;
+  ml_tensor_dimension dimension;
+
+  void *raw_data;
+  size_t data_size;
+  float *dataptr;
+
+  status = ml_tensors_info_get_count (info, &count);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_EQ (count, 1);
+
+  status = ml_tensors_info_get_tensor_type (info, 0, &type);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_EQ (type, ML_TENSOR_TYPE_FLOAT32);
+
+  status = ml_tensors_info_get_tensor_dimension (info, 0, dimension);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_EQ (dimension[0], 1);
+
+  status = ml_tensors_data_get_tensor_data (data, 0, &raw_data, &data_size);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  dataptr = (float *) raw_data;
+
+  if (vdata->negative) {
+    EXPECT_FALSE (dataptr[0] == vdata->golden[vdata->cursor][0] ||
+        dataptr[0] == vdata->golden[vdata->cursor + 1][0]);
+  } else {
+    EXPECT_TRUE (dataptr[0] == vdata->golden[vdata->cursor][0] ||
+        dataptr[0] == vdata->golden[vdata->cursor + 1][0]);
+  }
+
+  if (dataptr[0] == vdata->golden[vdata->cursor + 1][0])
+    vdata->cursor += 1;
+
+  vdata->checked += 1;
+}
+
+/**
+ * @brief Test pipeline creation and sink
+ */
+TEST (tizensensor_as_source, virtual_sensor_flow_03)
+{
+  gchar *pipeline;
+  sensor_event_s value;
+  sensor_h sensor;
+  int status = 0;
+  int count;
+  ml_pipeline_h handle;
+  ml_pipeline_sink_h s_handle;
+  ml_pipeline_state_e state;
+  verify_data data;
+
+  status = sensor_get_default_sensor (SENSOR_LIGHT, &sensor);
+  EXPECT_EQ (status, 0);
+
+  value.accuracy = 1;
+  value.timestamp = 0U;
+  value.value_count = 1;
+  value.values[0] = 0.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  data.checked = 0;
+  data.dim0 = 1;
+  data.type = ML_TENSOR_TYPE_FLOAT32;
+  data.cursor = 0;
+  data.num_data = 3;
+  data.golden[0][0] = 0.01;
+  data.golden[1][0] = 1.01;
+  data.golden[2][0] = 3.31;
+  data.negative = 0;
+
+  /* Create a nnstreamer pipeline */
+  pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=-1 num-buffers=50 framerate=100/1 ! tensor_sink name=getv");
+  status = ml_pipeline_construct (pipeline, NULL, NULL, &handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  status = ml_pipeline_sink_register (handle, "getv", callback_nns, &data, &s_handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_TRUE (s_handle != NULL);
+
+  status = ml_pipeline_start (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_usleep (10000); /* 10ms. Wait a bit. */
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  /* At this moment, it can be READY, PAUSED, or PLAYING */
+  EXPECT_NE (state, ML_PIPELINE_STATE_UNKNOWN);
+  EXPECT_NE (state, ML_PIPELINE_STATE_NULL);
+
+  count = 0;
+  while (state != ML_PIPELINE_STATE_PLAYING) {
+    g_usleep(1000); /* 1ms */
+    status = ml_pipeline_get_state (handle, &state);
+    EXPECT_EQ (status, ML_ERROR_NONE);
+    count++;
+    EXPECT_LE (count, 500);
+    if (count >= 500)
+      break;
+  }
+  g_usleep(10000); /* Let a frame or more flow */
+  value.values[0] = 1.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  g_usleep (100000); /* 100ms. Let a few frames flow. */
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_TRUE (state == ML_PIPELINE_STATE_PLAYING ||
+      state == ML_PIPELINE_STATE_PAUSED);
+  EXPECT_GT (data.checked, 1);
+
+  status = ml_pipeline_stop (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_usleep (10000); /* 10ms. Wait a bit. */
+
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_EQ (state, ML_PIPELINE_STATE_PAUSED);
+
+  status = ml_pipeline_sink_unregister (s_handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  status = ml_pipeline_destroy (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  g_free (pipeline);
+}
+
+/**
+ * @brief Test pipeline creation and sink
+ */
+TEST (tizensensor_as_source, virtual_sensor_flow_04)
+{
+  gchar *pipeline;
+  sensor_event_s value;
+  sensor_h sensor;
+  sensor_h *sensor_list;
+  int status = 0;
+  int count;
+  ml_pipeline_h handle;
+  ml_pipeline_sink_h s_handle;
+  ml_pipeline_state_e state;
+  verify_data data;
+
+  sensor_get_sensor_list (SENSOR_LIGHT, &sensor_list, &count);
+  EXPECT_EQ (count, 3);
+  sensor = sensor_list[2];
+  g_free (sensor_list);
+
+  value.accuracy = 1;
+  value.timestamp = 0U;
+  value.value_count = 1;
+  value.values[0] = 0.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  data.checked = 0;
+  data.dim0 = 1;
+  data.type = ML_TENSOR_TYPE_FLOAT32;
+  data.cursor = 0;
+  data.num_data = 3;
+  data.golden[0][0] = 0.01;
+  data.golden[1][0] = 1.01;
+  data.golden[2][0] = 3.31;
+  data.negative = 0;
+
+  /* Create a nnstreamer pipeline */
+  pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=2 num-buffers=50 framerate=100/1 ! tensor_sink name=getv");
+  status = ml_pipeline_construct (pipeline, NULL, NULL, &handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  status = ml_pipeline_sink_register (handle, "getv", callback_nns, &data, &s_handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_TRUE (s_handle != NULL);
+
+  status = ml_pipeline_start (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_usleep (10000); /* 10ms. Wait a bit. */
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  /* At this moment, it can be READY, PAUSED, or PLAYING */
+  EXPECT_NE (state, ML_PIPELINE_STATE_UNKNOWN);
+  EXPECT_NE (state, ML_PIPELINE_STATE_NULL);
+
+  count = 0;
+  while (state != ML_PIPELINE_STATE_PLAYING) {
+    g_usleep(10000); /* 10ms */
+    status = ml_pipeline_get_state (handle, &state);
+    EXPECT_EQ (status, ML_ERROR_NONE);
+    count++;
+    EXPECT_LE (count, 50);
+    if (count >= 50)
+      break;
+  }
+  g_usleep(10000); /* Let a frame or more flow */
+  value.values[0] = 1.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  g_usleep (100000); /* 100ms. Let a few frames flow. */
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_TRUE (state == ML_PIPELINE_STATE_PLAYING ||
+      state == ML_PIPELINE_STATE_PAUSED);
+  EXPECT_GT (data.checked, 1);
+
+  status = ml_pipeline_stop (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_usleep (10000); /* 10ms. Wait a bit. */
+
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_EQ (state, ML_PIPELINE_STATE_PAUSED);
+
+  status = ml_pipeline_sink_unregister (s_handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  status = ml_pipeline_destroy (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  g_free (pipeline);
+}
+
+/**
+ * @brief Test pipeline creation and sink (negative)
+ */
+TEST (tizensensor_as_source, virtual_sensor_flow_05_n)
+{
+  gchar *pipeline;
+  sensor_event_s value;
+  sensor_h sensor;
+  sensor_h *sensor_list;
+  int status = 0;
+  int count;
+  ml_pipeline_h handle;
+  ml_pipeline_sink_h s_handle;
+  ml_pipeline_state_e state;
+  verify_data data;
+
+  sensor_get_sensor_list (SENSOR_LIGHT, &sensor_list, &count);
+  EXPECT_EQ (count, 3);
+  sensor = sensor_list[2];
+
+  value.accuracy = 1;
+  value.timestamp = 0U;
+  value.value_count = 1;
+  value.values[0] = 0.00;
+  EXPECT_EQ (dummy_publish (sensor_list[0], value), 0);
+  EXPECT_EQ (dummy_publish (sensor_list[1], value), 0);
+  EXPECT_EQ (dummy_publish (sensor_list[2], value), 0);
+  g_free (sensor_list);
+
+  value.accuracy = 1;
+  value.timestamp = 0U;
+  value.value_count = 1;
+  value.values[0] = 0.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  data.checked = 0;
+  data.dim0 = 1;
+  data.type = ML_TENSOR_TYPE_FLOAT32;
+  data.cursor = 0;
+  data.num_data = 3;
+  data.golden[0][0] = 0.01;
+  data.golden[1][0] = 1.01;
+  data.golden[2][0] = 3.31;
+  data.negative = 1;
+
+  /* Create a nnstreamer pipeline */
+  pipeline = g_strdup_printf ("tensor_src_tizensensor type=SENSOR_LIGHT sequence=1 num-buffers=50 framerate=100/1 ! tensor_sink name=getv");
+  status = ml_pipeline_construct (pipeline, NULL, NULL, &handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  status = ml_pipeline_sink_register (handle, "getv", callback_nns, &data, &s_handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_TRUE (s_handle != NULL);
+
+  status = ml_pipeline_start (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_usleep (10000); /* 10ms. Wait a bit. */
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  /* At this moment, it can be READY, PAUSED, or PLAYING */
+  EXPECT_NE (state, ML_PIPELINE_STATE_UNKNOWN);
+  EXPECT_NE (state, ML_PIPELINE_STATE_NULL);
+
+  count = 0;
+  while (state != ML_PIPELINE_STATE_PLAYING) {
+    g_usleep(10000); /* 10ms */
+    status = ml_pipeline_get_state (handle, &state);
+    EXPECT_EQ (status, ML_ERROR_NONE);
+    count++;
+    EXPECT_LE (count, 50);
+    if (count >= 50)
+      break;
+  }
+  g_usleep(10000); /* Let a frame or more flow */
+  value.values[0] = 1.01;
+  EXPECT_EQ (dummy_publish (sensor, value), 0);
+
+  g_usleep (100000); /* 100ms. Let a few frames flow. */
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_TRUE (state == ML_PIPELINE_STATE_PLAYING ||
+      state == ML_PIPELINE_STATE_PAUSED);
+  EXPECT_GT (data.checked, 1);
+
+  status = ml_pipeline_stop (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  g_usleep (10000); /* 10ms. Wait a bit. */
+
+  status = ml_pipeline_get_state (handle, &state);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+  EXPECT_EQ (state, ML_PIPELINE_STATE_PAUSED);
+
+  status = ml_pipeline_sink_unregister (s_handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  status = ml_pipeline_destroy (handle);
+  EXPECT_EQ (status, ML_ERROR_NONE);
+
+  g_free (pipeline);
+}
+/**
+ * @brief Main GTest
+ */
+int main (int argc, char **argv)
+{
+  int result;
+
+  testing::InitGoogleTest (&argc, argv);
+  set_feature_state (1);
+
+  gst_init (&argc, &argv);
+  result = RUN_ALL_TESTS ();
+
+  set_feature_state (-1);
+  return result;
+}