/*
    ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
                 2011 Giovanni Di Sirio.
    This file is part of ChibiOS/RT.
    ChibiOS/RT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    ChibiOS/RT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/
#include "ch.h"
#include "test.h"
/**
 * @page test_mtx Mutexes test
 *
 * File: @ref testmtx.c
 *
 * 
Description
 * This module implements the test sequence for the @ref mutexes and
 * @ref condvars subsystems.
 * Tests on those subsystems are particularly critical because the system-wide
 * implications of the Priority Inheritance mechanism.
 *
 * Objective
 * Objective of the test module is to cover 100% of the subsystems code.
 *
 * Preconditions
 * The module requires the following kernel options:
 * - @p CH_USE_MUTEXES
 * - @p CH_USE_CONDVARS
 * - @p CH_DBG_THREADS_PROFILING
 * .
 * In case some of the required options are not enabled then some or all tests
 * may be skipped.
 *
 * Test Cases
 * - @subpage test_mtx_001
 * - @subpage test_mtx_002
 * - @subpage test_mtx_003
 * - @subpage test_mtx_004
 * - @subpage test_mtx_005
 * - @subpage test_mtx_006
 * - @subpage test_mtx_007
 * - @subpage test_mtx_008
 * .
 * @file testmtx.c
 * @brief Mutexes and CondVars test source file
 * @file testmtx.h
 * @brief Mutexes and CondVars test header file
 */
#if CH_USE_MUTEXES || defined(__DOXYGEN__)
#define ALLOWED_DELAY 5
/*
 * Note, the static initializers are not really required because the
 * variables are explicitly initialized in each test case. It is done in order
 * to test the macros.
 */
static MUTEX_DECL(m1);
static MUTEX_DECL(m2);
#if CH_USE_CONDVARS || defined(__DOXYGEN__)
static CONDVAR_DECL(c1);
#endif
/**
 * @page test_mtx_001 Priority enqueuing test
 *
 * Description
 * Five threads, with increasing priority, are enqueued on a locked mutex then
 * the mutex is unlocked.
 * The test expects the threads to perform their operations in increasing
 * priority order regardless of the initial order.
 */
static void mtx1_setup(void) {
  chMtxInit(&m1);
}
static msg_t thread1(void *p) {
  chMtxLock(&m1);
  test_emit_token(*(char *)p);
  chMtxUnlock();
  return 0;
}
static void mtx1_execute(void) {
  tprio_t prio = chThdGetPriority(); // Because priority inheritance.
  chMtxLock(&m1);
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread1, "E");
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread1, "D");
  threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread1, "C");
  threads[3] = chThdCreateStatic(wa[3], WA_SIZE, prio+4, thread1, "B");
  threads[4] = chThdCreateStatic(wa[4], WA_SIZE, prio+5, thread1, "A");
  chMtxUnlock();
  test_wait_threads();
  test_assert(1, prio == chThdGetPriority(), "wrong priority level");
  test_assert_sequence(2, "ABCDE");
}
ROMCONST struct testcase testmtx1 = {
  "Mutexes, priority enqueuing test",
  mtx1_setup,
  NULL,
  mtx1_execute
};
#if CH_DBG_THREADS_PROFILING || defined(__DOXYGEN__)
/**
 * @page test_mtx_002 Priority inheritance, simple case
 *
 * Description
 * Three threads are involved in the classic priority inversion scenario, a
 * medium priority thread tries to starve an high priority thread by
 * blocking a low priority thread into a mutex lock zone.
 * The test expects the threads to reach their goal in increasing priority
 * order by rearranging their priorities in order to avoid the priority
 * inversion trap.
 *
 * Scenario
 * This weird looking diagram should explain what happens in the test case:
 * @code
 * Time ----> 0     10    20    30    40    50    60    70    80    90    100
 *    0 ......AL++++++++++............2+++++++++++AU0---------------++++++G...
 *    1 ..................++++++++++++------------------++++++++++++G.........
 *    2  .............................AL..........++++++AUG...................
 *                                    ^           ^
 * Legend:
 *   0..2 - Priority levels
 *   +++  - Running
 *   ---  - Ready
 *   ...  - Waiting or Terminated
 *   xL   - Lock operation on mutex 'x'
 *   xUn  - Unlock operation on mutex 'x' with priority returning to level 'n'
 *   G    - Goal
 *   ^    - Priority transition (boost or return).
 * @endcode
 */
static void mtx2_setup(void) {
  chMtxInit(&m1);
}
/* Low priority thread */
static msg_t thread2L(void *p) {
  (void)p;
  chMtxLock(&m1);
  test_cpu_pulse(40);
  chMtxUnlock();
  test_cpu_pulse(10);
  test_emit_token('C');
  return 0;
}
/* Medium priority thread */
static msg_t thread2M(void *p) {
  (void)p;
  chThdSleepMilliseconds(20);
  test_cpu_pulse(40);
  test_emit_token('B');
  return 0;
}
/* High priority thread */
static msg_t thread2H(void *p) {
  (void)p;
  chThdSleepMilliseconds(40);
  chMtxLock(&m1);
  test_cpu_pulse(10);
  chMtxUnlock();
  test_emit_token('A');
  return 0;
}
static void mtx2_execute(void) {
  systime_t time;
  test_wait_tick();
  time = chTimeNow();
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()-1, thread2H, 0);
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, chThdGetPriority()-2, thread2M, 0);
  threads[2] = chThdCreateStatic(wa[2], WA_SIZE, chThdGetPriority()-3, thread2L, 0);
  test_wait_threads();
  test_assert_sequence(1, "ABC");
  test_assert_time_window(2, time + MS2ST(100), time + MS2ST(100) + ALLOWED_DELAY);
}
ROMCONST struct testcase testmtx2 = {
  "Mutexes, priority inheritance, simple case",
  mtx2_setup,
  NULL,
  mtx2_execute
};
/**
 * @page test_mtx_003 Priority inheritance, complex case
 *
 * Description
 * Five threads are involved in the complex priority inversion scenario,
 * please refer to the diagram below for the complete scenario.
 * The test expects the threads to perform their operations in increasing
 * priority order by rearranging their priorities in order to avoid the
 * priority inversion trap.
 *
 * Scenario
 * This weird looking diagram should explain what happens in the test case:
 * @code
 * Time ----> 0     10    20    30    40    50    60    70    80    90    100   110
 *    0 ......BL++++------------2+++++------4+++++BU0---------------------------G.....
 *    1 ............AL++++2+++++BL----------4-----++++++BU4+++AU1---------------G.....
 *    2 ..................AL----------------------------------------------++++++AUG...
 *    3 ..............................+++++++-----------------------++++++G...........
 *    4 ....................................AL................++++++AUG...............
 *                        ^     ^           ^     ^     ^     ^
 * Legend:
 *   0..4 - Priority levels
 *   +++  - Running
 *   ---  - Ready
 *   ...  - Waiting or Terminated
 *   xL   - Lock operation on mutex 'x'
 *   xUn  - Unlock operation on mutex 'x' with priority returning to level 'n'
 *   ^    - Priority transition (boost or return).
 * @endcode
 */
static void mtx3_setup(void) {
  chMtxInit(&m1); // Mutex B
  chMtxInit(&m2); // Mutex A
}
/* Lowest priority thread */
static msg_t thread3LL(void *p) {
  (void)p;
  chMtxLock(&m1);
  test_cpu_pulse(30);
  chMtxUnlock();
  test_emit_token('E');
  return 0;
}
/* Low priority thread */
static msg_t thread3L(void *p) {
  (void)p;
  chThdSleepMilliseconds(10);
  chMtxLock(&m2);
  test_cpu_pulse(20);
  chMtxLock(&m1);
  test_cpu_pulse(10);
  chMtxUnlock();
  test_cpu_pulse(10);
  chMtxUnlock();
  test_emit_token('D');
  return 0;
}
/* Medium priority thread */
static msg_t thread3M(void *p) {
  (void)p;
  chThdSleepMilliseconds(20);
  chMtxLock(&m2);
  test_cpu_pulse(10);
  chMtxUnlock();
  test_emit_token('C');
  return 0;
}
/* High priority thread */
static msg_t thread3H(void *p) {
  (void)p;
  chThdSleepMilliseconds(40);
  test_cpu_pulse(20);
  test_emit_token('B');
  return 0;
}
/* Highest priority thread */
static msg_t thread3HH(void *p) {
  (void)p;
  chThdSleepMilliseconds(50);
  chMtxLock(&m2);
  test_cpu_pulse(10);
  chMtxUnlock();
  test_emit_token('A');
  return 0;
}
static void mtx3_execute(void) {
  systime_t time;
  test_wait_tick();
  time = chTimeNow();
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()-5, thread3LL, 0);
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, chThdGetPriority()-4, thread3L, 0);
  threads[2] = chThdCreateStatic(wa[2], WA_SIZE, chThdGetPriority()-3, thread3M, 0);
  threads[3] = chThdCreateStatic(wa[3], WA_SIZE, chThdGetPriority()-2, thread3H, 0);
  threads[4] = chThdCreateStatic(wa[4], WA_SIZE, chThdGetPriority()-1, thread3HH, 0);
  test_wait_threads();
  test_assert_sequence(1, "ABCDE");
  test_assert_time_window(2, time + MS2ST(110), time + MS2ST(110) + ALLOWED_DELAY);
}
ROMCONST struct testcase testmtx3 = {
  "Mutexes, priority inheritance, complex case",
  mtx3_setup,
  NULL,
  mtx3_execute
};
#endif /* CH_DBG_THREADS_PROFILING */
/**
 * @page test_mtx_004 Priority return verification
 *
 * Description
 * Two threads are spawned that try to lock the mutexes locked by the tester
 * thread with precise timing.
 * The test expects that the priority changes caused by the priority
 * inheritance algorithm happen at the right moment and with the right values.
 */
static void mtx4_setup(void) {
  chMtxInit(&m1);
  chMtxInit(&m2);
}
static msg_t thread4a(void *p) {
  (void)p;
  chThdSleepMilliseconds(50);
  chMtxLock(&m2);
  chMtxUnlock();
  return 0;
}
static msg_t thread4b(void *p) {
  (void)p;
  chThdSleepMilliseconds(150);
  chMtxLock(&m1);
  chMtxUnlock();
  return 0;
}
static void mtx4_execute(void) {
  tprio_t p, p1, p2;
  p = chThdGetPriority();
  p1 = p + 1;
  p2 = p + 2;
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, p1, thread4a, "B");
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, p2, thread4b, "A");
  chMtxLock(&m2);
  test_assert(1, chThdGetPriority() == p, "wrong priority level");
  chThdSleepMilliseconds(100);
  test_assert(2, chThdGetPriority() == p1, "wrong priority level");
  chMtxLock(&m1);
  test_assert(3, chThdGetPriority() == p1, "wrong priority level");
  chThdSleepMilliseconds(100);
  test_assert(4, chThdGetPriority() == p2, "wrong priority level");
  chMtxUnlock();
  test_assert(5, chThdGetPriority() == p1, "wrong priority level");
  chThdSleepMilliseconds(100);
  test_assert(6, chThdGetPriority() == p1, "wrong priority level");
  chMtxUnlockAll();
  test_assert(7, chThdGetPriority() == p, "wrong priority level");
  test_wait_threads();
  /* Test repeated in order to cover chMtxUnlockS().*/
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, p1, thread4a, "D");
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, p2, thread4b, "C");
  chMtxLock(&m2);
  test_assert(8, chThdGetPriority() == p, "wrong priority level");
  chThdSleepMilliseconds(100);
  test_assert(9, chThdGetPriority() == p1, "wrong priority level");
  chMtxLock(&m1);
  test_assert(10, chThdGetPriority() == p1, "wrong priority level");
  chThdSleepMilliseconds(100);
  test_assert(11, chThdGetPriority() == p2, "wrong priority level");
  chSysLock();
  chMtxUnlockS();
  chSysUnlock();
  test_assert(12, chThdGetPriority() == p1, "wrong priority level");
  chThdSleepMilliseconds(100);
  test_assert(13, chThdGetPriority() == p1, "wrong priority level");
  chMtxUnlockAll();
  test_assert(14, chThdGetPriority() == p, "wrong priority level");
  test_wait_threads();
}
ROMCONST struct testcase testmtx4 = {
  "Mutexes, priority return",
  mtx4_setup,
  NULL,
  mtx4_execute
};
/**
 * @page test_mtx_005 Mutex status
 *
 * Description
 * Various tests on the mutex structure status after performing some lock and
 * unlock operations.
 * The test expects that the internal mutex status is consistent after each
 * operation.
 */
static void mtx5_setup(void) {
  chMtxInit(&m1);
}
static void mtx5_execute(void) {
  bool_t b;
  tprio_t prio;
  prio = chThdGetPriority();
  b = chMtxTryLock(&m1);
  test_assert(1, b, "already locked");
  b = chMtxTryLock(&m1);
  test_assert(2, !b, "not locked");
  chSysLock();
  chMtxUnlockS();
  chSysUnlock();
  test_assert(3, isempty(&m1.m_queue), "queue not empty");
  test_assert(4, m1.m_owner == NULL, "still owned");
  test_assert(5, chThdGetPriority() == prio, "wrong priority level");
  
  chMtxLock(&m1);
  chMtxUnlockAll();
  test_assert(6, isempty(&m1.m_queue), "queue not empty");
  test_assert(7, m1.m_owner == NULL, "still owned");
}
ROMCONST struct testcase testmtx5 = {
  "Mutexes, status",
  mtx5_setup,
  NULL,
  mtx5_execute
};
#if CH_USE_CONDVARS || defined(__DOXYGEN__)
/**
 * @page test_mtx_006 Condition Variable signal test
 *
 * Description
 * Five threads take a mutex and then enter a conditional variable queue, the
 * tester thread then proceeds to signal the conditional variable five times
 * atomically.
 * The test expects the threads to reach their goal in increasing priority
 * order regardless of the initial order.
 */
static void mtx6_setup(void) {
  chCondInit(&c1);
  chMtxInit(&m1);
}
static msg_t thread10(void *p) {
  chMtxLock(&m1);
  chCondWait(&c1);
  test_emit_token(*(char *)p);
  chMtxUnlock();
  return 0;
}
static void mtx6_execute(void) {
  tprio_t prio = chThdGetPriority();
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread10, "E");
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread10, "D");
  threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread10, "C");
  threads[3] = chThdCreateStatic(wa[3], WA_SIZE, prio+4, thread10, "B");
  threads[4] = chThdCreateStatic(wa[4], WA_SIZE, prio+5, thread10, "A");
  chSysLock();
  chCondSignalI(&c1);
  chCondSignalI(&c1);
  chCondSignalI(&c1);
  chCondSignalI(&c1);
  chCondSignalI(&c1);
  chSchRescheduleS();
  chSysUnlock();
  test_wait_threads();
  test_assert_sequence(1, "ABCDE");
}
ROMCONST struct testcase testmtx6 = {
  "CondVar, signal test",
  mtx6_setup,
  NULL,
  mtx6_execute
};
/**
 * @page test_mtx_007 Condition Variable broadcast test
 *
 * Description
 * Five threads take a mutex and then enter a conditional variable queue, the
 * tester thread then proceeds to broadcast the conditional variable.
 * The test expects the threads to reach their goal in increasing priority
 * order regardless of the initial order.
 */
static void mtx7_setup(void) {
  chCondInit(&c1);
  chMtxInit(&m1);
}
static void mtx7_execute(void) {
  // Bacause priority inheritance.
  tprio_t prio = chThdGetPriority();
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread10, "E");
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread10, "D");
  threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread10, "C");
  threads[3] = chThdCreateStatic(wa[3], WA_SIZE, prio+4, thread10, "B");
  threads[4] = chThdCreateStatic(wa[4], WA_SIZE, prio+5, thread10, "A");
  chCondBroadcast(&c1);
  test_wait_threads();
  test_assert_sequence(1, "ABCDE");
}
ROMCONST struct testcase testmtx7 = {
  "CondVar, broadcast test",
  mtx7_setup,
  NULL,
  mtx7_execute
};
/**
 * @page test_mtx_008 Condition Variable priority boost test
 *
 * Description
 * This test case verifies the priority boost of a thread waiting on a
 * conditional variable queue. It tests this very specific situation in order
 * to complete the code coverage.
 */
static void mtx8_setup(void) {
  chCondInit(&c1);
  chMtxInit(&m1);
  chMtxInit(&m2);
}
static msg_t thread11(void *p) {
  chMtxLock(&m2);
  chMtxLock(&m1);
#if CH_USE_CONDVARS_TIMEOUT || defined(__DOXYGEN__)
  chCondWaitTimeout(&c1, TIME_INFINITE);
#else
  chCondWait(&c1);
#endif
  test_emit_token(*(char *)p);
  chMtxUnlock();
  chMtxUnlock();
  return 0;
}
static msg_t thread12(void *p) {
  chMtxLock(&m2);
  test_emit_token(*(char *)p);
  chMtxUnlock();
  return 0;
}
static void mtx8_execute(void) {
  tprio_t prio = chThdGetPriority();
  threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread11, "A");
  threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread10, "C");
  threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread12, "B");
  chCondSignal(&c1);
  chCondSignal(&c1);
  test_wait_threads();
  test_assert_sequence(1, "ABC");
}
ROMCONST struct testcase testmtx8 = {
  "CondVar, boost test",
  mtx8_setup,
  NULL,
  mtx8_execute
};
#endif /* CH_USE_CONDVARS */
#endif /* CH_USE_MUTEXES */
/**
 * @brief   Test sequence for mutexes.
 */
ROMCONST struct testcase * ROMCONST patternmtx[] = {
#if CH_USE_MUTEXES || defined(__DOXYGEN__)
  &testmtx1,
#if CH_DBG_THREADS_PROFILING || defined(__DOXYGEN__)
  &testmtx2,
  &testmtx3,
#endif
  &testmtx4,
  &testmtx5,
#if CH_USE_CONDVARS || defined(__DOXYGEN__)
  &testmtx6,
  &testmtx7,
  &testmtx8,
#endif
#endif
  NULL
};