[GO] Développons quelque chose de proche de celui intégré avec TDD ~ Version de détection d'entrée de clé ~

introduction

En utilisant googletest / googlemock, je développe un logiciel qui fonctionne sur Linux embarqué avec TDD. Puisque la procédure réellement développée avec TDD est écrite telle quelle en temps réel, il y a une partie aléatoire. J'espère que vous apprécierez également le processus. Si vous avez des questions ou des erreurs, veuillez commenter. Ce sera encourageant.

Si vous souhaitez en savoir plus sur l'histoire jusqu'à présent, veuillez consulter les articles précédents. Essayez de développer quelque chose de proche de intégré avec TDD ~ Préparation ~ Essayez de développer quelque chose de proche de celui intégré avec TDD ~ Résolution de problèmes ~ Essayez de développer quelque chose de proche de celui intégré avec TDD ~ file open ~ Essayez de développer quelque chose de proche de celui intégré avec TDD ~ traitement d'initialisation / de terminaison de libevdev ~

input_event

Avant de continuer, vérifions la définition de structure utilisée dans l'événement d'entrée de clé.

struct input_event {
        struct timeval time;  //Heure d'entrée
        __u16 type;  //Type d'entrée. EV_KEY:Entrée clé, EV_REL:Mouvement de la souris
        __u16 code;  //Code d'entrée. CLÉ_A:Une clé, REL_X:Mouvement sur l'axe X de la souris
        __s32 value; // type==EV_Pour KEY, 1 lorsque la touche est enfoncée, 0 lorsqu'elle est relâchée
};

Dans la plage utilisée cette fois, le temps n'est pas utilisé. Si vous souhaitez enregistrer et reproduire des frappes, vous pouvez utiliser le temps. Cette fois, il vous suffit de savoir si elle a été enfoncée.

référence Événement_entrée du wiki Tummy

Détection d'entrée clé

Imaginez un test qui détecte une frappe particulière.

TEST_F(KeyInputEventTest, DetectCondition) {
  //Entrée signifiant que la touche «A» a été enfoncée_event
  constexpr input_event kPressA {timeval{}, EV_KEY, KEY_A, PUT_KEY_PRESSED};

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

Créez l'événement input_event que vous souhaitez détecter et enregistrez-le avec * SetKeyInputDetectCondition () *. Lorsque * CheckKeyInput () * est appelé, si l'événement d'entrée cible s'est produit, le code d'état correspondant sera renvoyé.

Maintenant, pour terminer le test, nous devons définir la valeur attendue de la maquette libevdev appelée * CheckKeyInput () *. Cette fois, l'API utilisée est un peu compliquée car elle a des ** effets secondaires **.

L'API pour obtenir des événements avec libevdev est la suivante.

int libevdev_next_event	(struct libevdev *dev,
                         unsigned int flags,  //Ignorez-le parce que c'est lié maintenant
                         struct input_event *ev)	

En cas de succès, la valeur de retour est LIBEVDEV_READ_STATUS_SUCCESS (0) et l'événement input_event qui s'est produit dans le troisième argument ev est défini. Autre que la valeur de retour, l'effet secondaire est que l'état change avant et après l'appel d'API. En langage C, passer un pointeur vers un paramètre et l'avoir rempli de données est assez naturel et fréquent. En raison des spécifications du langage, il n'y a pas d'autre choix que de concevoir une API qui provoque dans une certaine mesure de tels effets secondaires. Cependant, les API qui provoquent des effets secondaires doivent être réduites autant que possible. Les effets secondaires doivent également être évalués dans le test. Par conséquent, si vous utilisez beaucoup d'effets secondaires, vous devrez écrire beaucoup de valeurs attendues pour les appels API, ce qui rend les tests fastidieux. (En passant, si vous utilisez C ++, n'introduisez pas d'effets secondaires dans C. Si vous faites la même chose en C ++, vous faites une erreur de conception.)

Maintenant, jetons un coup d'œil à ce test. Lorsque * libevdev_next_event () * est appelé, input_event lorsque la touche "A" est enfoncée est défini dans le troisième argument.

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_));
}

C'est un peu difficile à lire, combiné à la faible efficacité des points forts de la syntaxe. Maintenant, l'action simulée * DoAll () * signifie exécuter toutes les actions multiples. Il y a deux actions à effectuer dans * DoAll () *:

De toute évidence, ce test ne se compile pas (est-il sur le point d'être de la merde?).

Tout d'abord, l'événement à détecter est enregistré par * SetKeyInputDetectCondition () * en ajoutant un membre à KeyInputDeviceStruct.

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

void SetKeyInputDetectCondition(KeyInputDevice dev,
                                const struct input_event *ev) {
  memcpy(&dev->target_event, ev, sizeof(struct input_event));
}
//Fonction d'assistance pour vérifier si un événement s'est produit
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_Fonction d'assistance pour comparer les structures d'événements
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;
}

Je n'ai montré que le résultat, mais la fonction d'assistance a été extraite à nouveau après avoir passé le test une fois. Grâce à ces deux fonctions d'assistance, vous pouvez écrire une expression conditionnelle dans une instruction if avec une condition claire que tout le monde peut voir: * HasPendingEvent () && IsTargetEvent () *. Le processus de refactoring n'est pas clairement montré, mais nous faisons toujours un si petit refactoring. Vous pouvez le faire parce que vous pouvez modifier votre code en toute sécurité grâce aux tests.

Ensuite, écrivez un test lorsqu'aucun événement ne s'est produit. Selon la spécification de l'API libevdev, * libevdev_next_event () * renvoie -EAGAIN s'il n'y a pas d'événements valides.

TEST_F(KeyInputEventTest, CannotDetectEvent) {
  //Si aucun événement ne s'est produit-EAGAIN est retourné
  EXPECT_CALL(*mock_libevdev, libevdev_next_event(_, _, _)).WillOnce(Return(-EAGAIN));

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

Testons un modèle légèrement plus compliqué. Le test suivant teste une séquence sans événement, deux événements sans intérêt et un événement que vous souhaitez détecter. Le code de test est compliqué et dégoûtant, mais j'ai un peu plus de patience.

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_));
}

Ce test réussit jusqu'à présent avec l'implémentation du code produit. Mettez à jour la liste des tâches.

Eh bien, j'ai pu mettre en œuvre tout ce que je voulais faire avec la détection des entrées clés. J'ai attendu un moment. Rappelez-vous qu'il y a un élément que vous avez oublié?

Après Cleanup (), l'utilisation de libevdev échoue

La dernière fois, j'ai vu une spécification importante dans le processus de terminaison de libevdev et je l'ai ajoutée à la liste ToDo.

Examinons de plus près le processus de terminaison de libevdev, * libevdev_free () *.

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

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

Au fait, quel genre de comportement fait free () en langage C? Je ne suis pas sûr de ma mémoire. Certes, même avec free (), le point pointé par le pointeur aurait dû rester le même.

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

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

Le souvenir était certain. Le test ci-dessus réussit.

Afficher le code de test et de produit.

Code de test

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

  auto dev = CreateKeyInputDevice();
  CleanupKeyInputDevice(dev);
  // Cleanup()Échec de la détection des entrées clés du développement effectué
  EXPECT_EQ(INPUT_DEV_INVALID_DEV, CheckKeyInput(dev));

  DestroyKeyInputDevice(dev);
}

Code produit

int CheckKeyInput(KeyInputDevice dev) {
  //Ajout de la garde au pointeur NULL d'evdev
  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;  //ajouter à
  int rc = IO_CLOSE(dev->fd);
  if (rc < 0) return INPUT_DEV_CLEANUP_ERROR;

  return INPUT_DEV_SUCCESS;
}

Maintenant, après avoir appelé libevdev_free (), les appels d'API qui utilisent la structure libevdev échoueront.

Disposition de la détection des entrées clés

La détection de la saisie par clé est désormais possible. Les API qui provoquent des effets secondaires peuvent également être testées en utilisant un puits simulé.

Code de test

Ajout de 4 nouveaux tests. Au total, 12 tests ont réussi. Le code est omis car la coloration syntaxique ne fonctionne pas et le code est difficile à voir. github est un peu mieux, donc si vous voulez voir tout le code de test, veuillez aller à ↓. github:key_input_event_test.cpp

Code produit

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 (Appuyez deux fois sur la touche "A" pour terminer le programme)

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;
}

Liste de choses à faire

L'état de la liste des tâches concernant l'entrée des touches est le suivant. Les éléments énumérés au début ont pris fin.

Aperçu de la prochaine fois

Nous prévoyons de faire secrètement le contrôle des LED dans les coulisses et de publier uniquement les résultats. Je pense que ce sera plus facile à faire que le processus d'entrée clé. S'il y a quelque chose de spécial à mentionner, j'ajouterai une petite explication.

La prochaine fois, je comparerai le programme créé dans la section de résolution de problèmes avec le programme créé dans TDD. En même temps, j'aimerais penser à une future expansion.

Recommended Posts

Développons quelque chose de proche de celui intégré avec TDD ~ Version de détection d'entrée de clé ~
Développons quelque chose qui se rapproche du TDD
Développons quelque chose de proche de intégré avec TDD ~ Revue intermédiaire ~
Essayez de développer quelque chose de proche de l'intégration avec TDD ~ Problème ~
Développons quelque chose de proche de celui intégré avec TDD ~ Design pattern ~
Développons quelque chose de proche de celui intégré avec TDD ~ édition ouverte de fichier ~
Développons quelque chose de proche de l'implémentation avec TDD ~ traitement d'initialisation / de terminaison de libevdev ~
Convertir mp4 en mp3 avec ffmpeg (version miniature intégrée)