[GO] Lassen Sie uns mit TDD ~ Key Input Detection Version ~ etwas entwickeln, das dem Integrierten nahe kommt

Einführung

Mit googletest / googlemock entwickle ich Software, die unter Embedded Linux mit TDD läuft. Da die mit TDD tatsächlich entwickelte Prozedur so geschrieben ist, wie sie in Echtzeit ist, gibt es einen zufälligen Teil. Ich hoffe, Sie genießen den Prozess auch. Wenn Sie Fragen oder Fehler haben, kommentieren Sie bitte. Es wird ermutigend sein.

Wenn Sie mehr über die bisherige Geschichte erfahren möchten, lesen Sie bitte die vorherigen Artikel. Versuchen Sie, etwas zu entwickeln, das mit TDD ~ Preparation ~ nahezu eingebettet ist Versuchen Sie, etwas zu entwickeln, das mit TDD nahezu eingebettet ist ~ Problemauslösung ~ Versuchen Sie, mit TDD ~ file open ~ etwas zu entwickeln, das nahezu eingebettet ist Versuchen Sie, mit TDD ~ libevdev Initialisierungs- / Beendigungsverarbeitung ~ etwas zu entwickeln, das nahezu eingebettet ist ~

input_event

Bevor wir fortfahren, überprüfen wir die Strukturdefinition, die im Schlüsseleingabeereignis verwendet wird.

struct input_event {
        struct timeval time;  //Zeitpunkt der Eingabe
        __u16 type;  //Eingabetyp. EV_KEY:Tasteneingabe, EV_REL:Mausbewegung
        __u16 code;  //Code eingeben. SCHLÜSSEL_A:Ein Schlüssel, REL_X:Bewegung der Maus-X-Achse
        __s32 value; // type==EV_Im Fall von KEY 1 beim Drücken der Taste und 0 beim Loslassen
};

In dem diesmal verwendeten Bereich wird keine Zeit verwendet. Wenn Sie Tastenanschläge aufzeichnen und reproduzieren möchten, können Sie die Zeit verwenden. Dieses Mal müssen Sie nur wissen, ob es gedrückt wurde.

Referenz Tummy Wiki input_event

Tasteneingabeerkennung

Stellen Sie sich einen Test vor, der einen bestimmten Tastendruck erkennt.

TEST_F(KeyInputEventTest, DetectCondition) {
  //Eingabe bedeutet, dass die Taste "A" gedrückt wurde_event
  constexpr input_event kPressA {timeval{}, EV_KEY, KEY_A, PUT_KEY_PRESSED};

  SetKeyInputDetectCondition(dev_, &kPressA);
  EXPECT_EQ(INPUT_DEV_EVENT_DETECTED, CheckKeyInput(dev_));
}

Erstellen Sie das zu erkennende input_event und registrieren Sie es bei * SetKeyInputDetectCondition () *. Wenn * CheckKeyInput () * aufgerufen wird und das Zieleingabeereignis aufgetreten ist, wird der entsprechende Statuscode zurückgegeben.

Um den Test abzuschließen, müssen wir den erwarteten Wert des libevdev-Mocks mit dem Namen * CheckKeyInput () * festlegen. Diesmal ist es etwas kompliziert, da die verwendete API ** Nebenwirkungen ** hat.

Die API zum Abrufen von Ereignissen mit libevdev lautet wie folgt.

int libevdev_next_event	(struct libevdev *dev,
                         unsigned int flags,  //Ignoriere es, weil es jetzt verwandt ist
                         struct input_event *ev)	

Bei Erfolg lautet der Rückgabewert LIBEVDEV_READ_STATUS_SUCCESS (0), und das im dritten Argument ev aufgetretene input_event wird festgelegt. Abgesehen vom Rückgabewert besteht der Nebeneffekt darin, dass sich der Status vor und nach dem API-Aufruf ändert. In der Sprache C ist es ganz natürlich und häufig, einen Zeiger auf einen Parameter zu übergeben und ihn mit Daten zu packen. Aufgrund der Sprachspezifikationen bleibt keine andere Wahl, als eine API zu entwerfen, die solche Nebenwirkungen in gewissem Maße verursacht. APIs, die Nebenwirkungen verursachen, sollten jedoch so weit wie möglich reduziert werden. Nebenwirkungen müssen auch im Test bewertet werden. Wenn daher häufig Nebenwirkungen verwendet werden, müssen viele erwartete Werte für API-Aufrufe geschrieben werden, was den Test umständlich macht. (Abgesehen davon, wenn Sie C ++ verwenden, bringen Sie keine Nebenwirkungen in C. Wenn Sie dasselbe in C ++ tun, machen Sie einen Designfehler.)

Schauen wir uns diesen Test an. Wenn * libevdev_next_event () * aufgerufen wird, wird im dritten Argument input_event beim Drücken der Taste "A" gesetzt.

TEST_F(KeyInputEventTest, DetectCondition) {
  input_event kPressA {timeval{}, EV_KEY, KEY_A, INPUT_KEY_PRESSED};

  EXPECT_CALL(*mock_libevdev, libevdev_next_event(_, _, _)).WillOnce(
    DoAll(SetArgPointee<2>(kPressA), Return(LIBEVDEV_READ_STATUS_SUCCESS)));

  SetKeyInputDetectCondition(dev_, &kPressA);
  EXPECT_TRUE(KeyInputDetected(dev_));
}

Es ist etwas schwer zu lesen, kombiniert mit der schlechten Effektivität der Syntax-Highlights. Die Scheinaktion * DoAll () * bedeutet nun, alle mehreren Aktionen auszuführen. In * DoAll () * müssen zwei Aktionen ausgeführt werden:

Offensichtlich wird dieser Test nicht kompiliert (Wird es Mist sein?).

Zunächst wird das zu erkennende Ereignis von * SetKeyInputDetectCondition () * aufgezeichnet, indem ein Mitglied zu KeyInputDeviceStruct hinzugefügt wird.

typedef struct KeyInputDeviceStruct {
  int fd;
  struct libevdev *evdev;
  struct input_event target_event;  //hinzufügen
} KeyInputDeviceStruct;

void SetKeyInputDetectCondition(KeyInputDevice dev,
                                const struct input_event *ev) {
  memcpy(&dev->target_event, ev, sizeof(struct input_event));
}
//Hilfsfunktion zur Überprüfung, ob ein Ereignis aufgetreten ist
static bool HasPendingEvent(struct libevdev *evdev, struct input_event *ev) {
  return libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, ev)
          == LIBEVDEV_READ_STATUS_SUCCESS;
}

// input_Hilfsfunktion zum Vergleichen von Ereignisstrukturen
static bool IsTargetEvent(const struct input_event *target,
                          const struct input_event *ev) {
  return target->type == ev->type
      && target->code == ev->code
      && target->value == ev->value;
}

bool CheckKeyInput(KeyInputDevice dev) {
  struct input_event ev = {};
  if (HasPendingEvent(dev->evdev, &ev) && IsTargetEvent(&dev->target_event, &ev)) {
    return INPUT_DEV_EVENT_DETECTED;
  }
  return INPUT_DEV_NO_EVENT;
}

Ich habe nur das Ergebnis gezeigt, aber die Hilfsfunktion wurde nach einmaligem Bestehen des Tests erneut extrahiert. Dank dieser beiden Hilfsfunktionen ist es möglich, einen bedingten Ausdruck in eine if-Anweisung mit einer eindeutigen Bedingung zu schreiben, die jeder sehen kann: * HasPendingEvent () && IsTargetEvent () *. Der Prozess des Refactorings ist nicht klar dargestellt, aber wir führen immer so kleine Refactorings durch. Sie können dies tun, da es dank Tests sicher ist, Ihren Code zu ändern.

Schreiben Sie als Nächstes einen Test, wenn kein Ereignis aufgetreten ist. Gemäß der API-Spezifikation von libevdev gibt * libevdev_next_event () * -EAGAIN zurück, wenn keine gültigen Ereignisse vorhanden sind.

TEST_F(KeyInputEventTest, CannotDetectEvent) {
  //Wenn kein Ereignis aufgetreten ist-EAGAIN wird zurückgegeben
  EXPECT_CALL(*mock_libevdev, libevdev_next_event(_, _, _)).WillOnce(Return(-EAGAIN));

  SetKeyInputDetectCondition(dev_, &kPressA);
  EXPECT_EQ(INPUT_DEV_NO_EVENT, CheckKeyInput(dev_));
}

Testen wir ein etwas komplizierteres Muster. Der nächste Test testet eine Folge von Ereignissen ohne Ereignis, zwei uninteressante Ereignisse und ein Ereignis, das Sie erkennen möchten. Der Testcode ist kompliziert und widerlich, aber ich habe etwas mehr Geduld.

TEST_F(KeyInputEventTest, DetectOnlyInterestedEvent) {
  constexpr input_event kPressB {timeval{}, EV_KEY, KEY_B, INPUT_KEY_PRESSED};
  constexpr input_event kReleaseA {timeval{}, EV_KEY, KEY_A, INPUT_KEY_RELEASED};
  constexpr auto kSuccess = LIBEVDEV_READ_STATUS_SUCCESS;

  EXPECT_CALL(*mock_libevdev, libevdev_next_event(_, _, _))
    .WillOnce(Return(-EAGAIN))
    .WillOnce(DoAll(SetArgPointee<2>(kPressB), Return(kSuccess)))
    .WillOnce(DoAll(SetArgPointee<2>(kReleaseA), Return(kSuccess)))
    .WillOnce(DoAll(SetArgPointee<2>(kPressA), Return(kSuccess)));

  SetKeyInputDetectCondition(dev_, &kPressA);
  EXPECT_FALSE(KeyInputDetected(dev_));
  EXPECT_FALSE(KeyInputDetected(dev_));
  EXPECT_FALSE(KeyInputDetected(dev_));
  EXPECT_TRUE(KeyInputDetected(dev_));
}

Dieser Test besteht mit der bisherigen Implementierung des Produktcodes. Aktualisieren Sie die Aufgabenliste.

Nun, ich konnte alles implementieren, was ich mit der Erkennung von Schlüsseleingaben tun wollte. Ich habe eine Weile gewartet. Denken Sie daran, dass Sie einen Gegenstand vergessen haben?

Nach Cleanup () schlägt die Verwendung von libevdev fehl

Beim letzten Mal habe ich eine wichtige Spezifikation im Beendigungsprozess von libevdev gesehen und sie der ToDo-Liste hinzugefügt.

Schauen wir uns den Beendigungsprozess von libevdev * libevdev_free () * genauer an.

LIBEVDEV_EXPORT void
libevdev_free(struct libevdev *dev)
{
	if (!dev)
		return;

	queue_free(dev);
	libevdev_reset(dev);
	free(dev);
}

Übrigens, welche Art von Verhalten hat free () in der C-Sprache bewirkt? Ich bin mir meiner Erinnerung nicht sicher. Selbst mit free () sollte der Punkt, auf den der Zeiger zeigt, derselbe bleiben.

TEST_F(KeyInputEventDetectionTest, TestFree) {
  int *mem = static_cast<int*>(calloc(1, sizeof(int)));
  int *tmp = mem;

  free(mem);
  EXPECT_EQ(tmp, mem);
}

Die Erinnerung war sicher. Der obige Test besteht.

Test- und Produktcode anzeigen.

Testcode

TEST_F(KeyInputEventDetectionTest, FailOperationAfterCleanup) {
  EXPECT_CALL(*mock_libevdev, libevdev_free(_)).Times(1);

  auto dev = CreateKeyInputDevice();
  CleanupKeyInputDevice(dev);
  // Cleanup()Die Erkennung der Schlüsseleingabe von dev, die durchgeführt wurde, schlägt fehl
  EXPECT_EQ(INPUT_DEV_INVALID_DEV, CheckKeyInput(dev));

  DestroyKeyInputDevice(dev);
}

Produktcode

int CheckKeyInput(KeyInputDevice dev) {
  //Schutz für evdevs NULL-Zeiger hinzugefügt
  if (dev == NULL || dev->evdev == NULL) return INPUT_DEV_INVALID_DEV;
  struct input_event ev = {};
  if (HasPendingEvent(dev->evdev, &ev) && IsTargetEvent(&dev->target_event, &ev)) {
    return INPUT_DEV_EVENT_DETECTED;
  }
  return INPUT_DEV_NO_EVENT;
}

int CleanupKeyInputDevice(KeyInputDevice dev) {
  if(dev == NULL) return INPUT_DEV_INVALID_DEV;

  libevdev_free(dev->evdev);
  dev->evdev = NULL;  //hinzufügen
  int rc = IO_CLOSE(dev->fd);
  if (rc < 0) return INPUT_DEV_CLEANUP_ERROR;

  return INPUT_DEV_SUCCESS;
}

Nach dem Aufruf von libevdev_free () schlagen API-Aufrufe, die die libevdev-Struktur verwenden, fehl.

Anordnung der Tasteneingabeerkennung

Die Erkennung von Tasteneingaben ist jetzt möglich. APIs, die Nebenwirkungen verursachen, können auch mithilfe des Mock-Wells getestet werden.

Testcode

4 neue Tests hinzugefügt. Insgesamt wurden 12 Tests bestanden. Der Code wird weggelassen, da die Syntaxhervorhebung nicht funktioniert und der Code schwer zu erkennen ist. github ist etwas besser. Wenn Sie also den gesamten Testcode sehen möchten, gehen Sie bitte zu ↓. github:key_input_event_test.cpp

Produktcode

typedef struct KeyInputDeviceStruct {
  int fd;
  struct libevdev *evdev;
  struct input_event target_event;
} KeyInputDeviceStruct;

KeyInputDevice CreateKeyInputDevice() {
  KeyInputDevice dev = calloc(1, sizeof(KeyInputDeviceStruct));
  dev->fd = -1;
  dev->evdev = NULL;

  return dev;
}

int InitKeyInputDevice(KeyInputDevice dev, const char *device_file) {
  if(dev == NULL) return INPUT_DEV_INVALID_DEV;

  dev->fd = IO_OPEN(device_file, O_RDONLY|O_NONBLOCK);
  if (dev->fd < 0) {
    if (errno == EACCES)
      DEBUG_LOG("Fail to open file. You may need root permission.");
    return INPUT_DEV_INIT_ERROR;
  }

  int rc = libevdev_new_from_fd(dev->fd, &dev->evdev);
  if (rc < 0) return INPUT_DEV_INIT_ERROR;

  return INPUT_DEV_SUCCESS;
}

int SetKeyInputDetectCondition(KeyInputDevice dev, const struct input_event *ev) {
  if (dev == NULL) return INPUT_DEV_INVALID_DEV;
  // Should I validate ev, here?
  memcpy(&dev->target_event, ev, sizeof(struct input_event));
  return INPUT_DEV_SUCCESS;
}

static bool HasPendingEvent(struct libevdev *evdev, struct input_event *ev) {
  return libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, ev)
          == LIBEVDEV_READ_STATUS_SUCCESS;
}

static bool IsTargetEvent(const struct input_event *target,
                          const struct input_event *ev) {
  return (target->type == ev->type
       && target->code == ev->code
       && target->value == ev->value);
}

int CheckKeyInput(KeyInputDevice dev) {
  if (dev == NULL || dev->evdev == NULL) return INPUT_DEV_INVALID_DEV;
  struct input_event ev = {};
  if (HasPendingEvent(dev->evdev, &ev) && IsTargetEvent(&dev->target_event, &ev)) {
    return INPUT_DEV_EVENT_DETECTED;
  }
  return INPUT_DEV_NO_EVENT;
}

int CleanupKeyInputDevice(KeyInputDevice dev) {
  if(dev == NULL) return INPUT_DEV_INVALID_DEV;

  libevdev_free(dev->evdev);
  dev->evdev = NULL;
  int rc = IO_CLOSE(dev->fd);
  if (rc < 0) return INPUT_DEV_CLEANUP_ERROR;

  return INPUT_DEV_SUCCESS;
}

void DestroyKeyInputDevice(KeyInputDevice dev) {
  if(dev == NULL) return;

  free(dev);
  dev = NULL;
}

main.c (Drücken Sie zweimal die Taste "A", um das Programm zu beenden.)

int main(void) {
  KeyInputDevice dev = CreateKeyInputDevice();
  InitKeyInputDevice(dev, "/dev/input/event2");
  struct timeval time = {};
  const struct input_event kPressA = {time, EV_KEY, KEY_A, INPUT_KEY_PRESSED};
  SetKeyInputDetectCondition(dev, &kPressA);

  int count = 0;
  while(count < 2) {
    if(CheckKeyInput(dev) == INPUT_DEV_EVENT_DETECTED) count++;
  }

  CleanupKeyInputDevice(dev);
  DestroyKeyInputDevice(dev);

  return 0;
}

Aufgabenliste

Der Status der Aufgabenliste bezüglich der Tasteneingabe lautet wie folgt. Die am Anfang aufgeführten Punkte sind beendet.

Vorschau beim nächsten Mal

Wir planen, die LED-Steuerung heimlich hinter den Kulissen vorzunehmen und nur die Ergebnisse zu veröffentlichen. Ich denke, es wird einfacher zu machen sein als der Schlüsseleingabeprozess. Wenn es etwas Besonderes zu erwähnen gibt, werde ich eine kleine Erklärung hinzufügen.

Das nächste Mal werde ich das im Abschnitt zur Problemlösung erstellte Programm mit dem in TDD erstellten Programm vergleichen. Gleichzeitig möchte ich über eine zukünftige Expansion nachdenken.

Recommended Posts

Lassen Sie uns mit TDD ~ Key Input Detection Version ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns mit TDD ~ Preparation ~ etwas entwickeln, das nahezu eingebettet ist
Lassen Sie uns mit TDD ~ Intermediate Review ~ etwas entwickeln, das dem Integrierten nahe kommt
Versuchen Sie, etwas zu entwickeln, das der Einbettung mit TDD nahe kommt ~ Problemauslösung ~
Lassen Sie uns mit TDD ~ Design pattern ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns etwas entwickeln, das mit TDD ~ file open edition ~ nahezu eingebettet ist
Lassen Sie uns etwas entwickeln, das mit TDD ~ libevdev Initialisierungs- / Beendigungsverarbeitung ~ nahezu eingebettet ist
Konvertieren Sie mp4 in mp3 mit ffmpeg (eingebettete Thumbnail-Version)