Ticket #762: Updated JB test:
 - Added support for more scenarios, e.g: frame loss, late, sequence restart, etc.
 - Added automatic test result checks based on specified condition.
 - Updated test data.




git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2579 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/src/test/jbuf_test.c b/pjmedia/src/test/jbuf_test.c
index c8a5681..eac92db 100644
--- a/pjmedia/src/test/jbuf_test.c
+++ b/pjmedia/src/test/jbuf_test.c
@@ -26,107 +26,283 @@
 #define JB_MIN_PREFETCH	    0
 #define JB_MAX_PREFETCH	    10
 #define JB_PTIME	    20
-#define JB_BUF_SIZE	    20
+#define JB_BUF_SIZE	    50
 
 #define REPORT
 //#define PRINT_COMMENT
 
+typedef struct test_param_t {
+    pj_bool_t adaptive;
+    unsigned init_prefetch;
+    unsigned min_prefetch;
+    unsigned max_prefetch;
+} test_param_t;
+
+typedef struct test_cond_t {
+    int burst;
+    int discard;
+    int lost;
+    int empty;
+    int delay;	    /**< Maximum delay, in frames.	    */
+} test_cond_t;
+
+static pj_bool_t parse_test_headers(char *line, test_param_t *param, 
+			       test_cond_t *cond)
+{
+    char *p = line;
+
+    if (*p == '%') {
+	/* Test params. */
+	char mode_st[16];
+
+	sscanf(p+1, "%s %u %u %u", mode_st, &param->init_prefetch, 
+	       &param->min_prefetch, &param->max_prefetch);
+	param->adaptive = (stricmp(mode_st, "adaptive") == 0);
+
+    } else if (*p == '!') {
+	/* Success condition. */
+	char cond_st[16];
+	unsigned cond_val;
+
+	sscanf(p+1, "%s %u", cond_st, &cond_val);
+	if (stricmp(cond_st, "burst") == 0)
+	    cond->burst = cond_val;
+	else if (stricmp(cond_st, "delay") == 0)
+	    cond->delay = cond_val;
+	else if (stricmp(cond_st, "discard") == 0)
+	    cond->discard = cond_val;
+	else if (stricmp(cond_st, "empty") == 0)
+	    cond->empty = cond_val;
+	else if (stricmp(cond_st, "lost") == 0)
+	    cond->lost = cond_val;
+
+    } else if (*p == '=') {
+	/* Test title. */
+	++p;
+	while (*p && isspace(*p)) ++p;
+	printf("%s", p);
+    } else {
+	/* Unknown header, perhaps this is the test data */
+
+	/* Skip spaces */
+	while (*p && isspace(*p)) ++p;
+
+	/* Test data started.*/
+	if (*p != 0)
+	    return PJ_FALSE;
+    }
+
+    return PJ_TRUE;
+}
+
+static pj_bool_t process_test_data(char data, pjmedia_jbuf *jb, 
+				   pj_uint16_t *seq, pj_uint16_t *last_seq)
+{
+    char frame[1];
+    char f_type;
+    pj_bool_t print_state = PJ_TRUE;
+    pj_bool_t data_eos = PJ_FALSE;
+
+    switch (toupper(data)) {
+    case 'G': /* Get */
+	pjmedia_jbuf_get_frame(jb, frame, &f_type);
+	break;
+    case 'P': /* Put */
+	pjmedia_jbuf_put_frame(jb, (void*)frame, 1, *seq);
+	*last_seq = *seq;
+	++*seq;
+	break;
+    case 'L': /* Lost */
+	*last_seq = *seq;
+	++*seq;
+	printf("Lost\n");
+	break;
+    case 'R': /* Sequence restarts */
+	*seq = 1;
+	printf("Sequence restarting, from %u to %u\n", *last_seq, *seq);
+	break;
+    case 'J': /* Sequence jumps */
+	(*seq) += 5000;
+	printf("Sequence jumping, from %u to %u\n", *last_seq, *seq);
+	break;
+    case 'D': /* Frame duplicated */
+	pjmedia_jbuf_put_frame(jb, (void*)frame, 1, *seq - 1);
+	break;
+    case 'O': /* Old/late frame */
+	pjmedia_jbuf_put_frame(jb, (void*)frame, 1, *seq - 10 - pj_rand()%40);
+	break;
+    case '.': /* End of test session. */
+	data_eos = PJ_TRUE;
+	break;
+    default:
+	print_state = PJ_FALSE;
+	printf("Unknown test data '%c'\n", data);
+	break;
+    }
+
+    if (data_eos)
+	return PJ_FALSE;
+
+#ifdef REPORT
+    if (print_state) {
+	pjmedia_jb_state state;
+
+	pjmedia_jbuf_get_state(jb, &state);
+	printf("seq=%d\t%c\tsize=%d\tprefetch=%d\n", 
+	       *last_seq, toupper(data), state.size, state.prefetch);
+    }
+#endif
+
+    return PJ_TRUE;
+}
+
 int jbuf_main(void)
 {
-    pjmedia_jbuf *jb;
     FILE *input = fopen("JBTEST.DAT", "rt");
-    unsigned seq;
-    char line[1024 * 10], *p;
-    pj_pool_t *pool;
-    pjmedia_jb_state state;
-    pj_str_t jb_name = {"JBTEST", 6};
+    pj_bool_t data_eof = PJ_FALSE;
+    int old_log_level;
+    int rc = 0;
 
-    pj_init();
-    pool = pj_pool_create(mem, "JBPOOL", 256*16, 256*16, NULL);
+    old_log_level = pj_log_get_level();
+    pj_log_set_level(5);
 
-    pjmedia_jbuf_create(pool, &jb_name, 1, JB_PTIME, JB_BUF_SIZE, &jb);
-    pjmedia_jbuf_set_adaptive(jb, JB_INIT_PREFETCH, JB_MIN_PREFETCH, 
-			      JB_MAX_PREFETCH);
+    while (rc == 0 && !data_eof) {
+	pj_str_t jb_name = {"JBTEST", 6};
+	pjmedia_jbuf *jb;
+	pj_pool_t *pool;
+	pjmedia_jb_state state;
+	pj_uint16_t last_seq = 0;
+	pj_uint16_t seq = 1;
+	char line[1024], *p = NULL;
 
-    while ((p=fgets(line, sizeof(line), input)) != NULL) {
+	test_param_t param;
+	test_cond_t cond;
 
-	while (*p && isspace(*p))
-	    ++p;
+	param.adaptive = PJ_TRUE;
+	param.init_prefetch = JB_INIT_PREFETCH;
+	param.min_prefetch = JB_MIN_PREFETCH;
+	param.max_prefetch = JB_MAX_PREFETCH;
 
-	if (!*p)
-	    continue;
+	cond.burst = -1;
+	cond.delay = -1;
+	cond.discard = -1;
+	cond.empty = -1;
+	cond.lost = -1;
 
-	if (*p == '#') {
-#ifdef PRINT_COMMENT
-	    printf("%s", p);
-#endif
-	    continue;
-	}
+	printf("\n\n");
 
+	/* Parse test session title, param, and conditions */
+	do {
+	    p = fgets(line, sizeof(line), input);
+	} while (p && parse_test_headers(line, &param, &cond));
+
+	/* EOF test data */
+	if (p == NULL)
+	    break;
+
+	//printf("======================================================\n");
+
+	/* Initialize test session */
+	pool = pj_pool_create(mem, "JBPOOL", 256*16, 256*16, NULL);
+	pjmedia_jbuf_create(pool, &jb_name, 1, JB_PTIME, JB_BUF_SIZE, &jb);
 	pjmedia_jbuf_reset(jb);
-	seq = 1;
+
+	if (param.adaptive) {
+	    pjmedia_jbuf_set_adaptive(jb, 
+				      param.init_prefetch, 
+				      param.min_prefetch,
+				      param.max_prefetch);
+	} else {
+	    pjmedia_jbuf_set_fixed(jb, param.init_prefetch);
+	}
 
 #ifdef REPORT
 	pjmedia_jbuf_get_state(jb, &state);
 	printf("Initial\tsize=%d\tprefetch=%d\tmin.pftch=%d\tmax.pftch=%d\n", 
-	    state.size, state.prefetch, state.min_prefetch, state.max_prefetch);
+	       state.size, state.prefetch, state.min_prefetch, 
+	       state.max_prefetch);
 #endif
 
-	while (*p) {
+
+	/* Test session start */
+	while (1) {
 	    int c;
-	    char frame[1];
-	    char f_type;
 	    
+	    /* Get next line of test data */
+	    if (!p || *p == 0) {
+		p = fgets(line, sizeof(line), input);
+		if (p == NULL) {
+		    data_eof = PJ_TRUE;
+		    break;
+		}
+	    }
+
+	    /* Get next char of test data */
 	    c = *p++;
+
+	    /* Skip spaces */
 	    if (isspace(c))
 		continue;
-	    
-	    if (c == '/') {
-		putchar('\n');
 
-		while (*++p && *p != '/')
-		    putchar(*p);
-
-		putchar('\n');
-
-		if (*++p == 0)
-		    break;
-
+	    /* Print comment line */
+	    if (c == '#') {
+#ifdef PRINT_COMMENT
+		while (*p && isspace(*p)) ++p;
+		if (*p) printf("..%s", p);
+#endif
+		*p = 0;
 		continue;
 	    }
 
-	    switch (toupper(c)) {
-	    case 'G':
-		pjmedia_jbuf_get_frame(jb, frame, &f_type);
+	    /* Process test data */
+	    if (!process_test_data(c, jb, &seq, &last_seq))
 		break;
-	    case 'P':
-		pjmedia_jbuf_put_frame(jb, (void*)frame, 1, seq);
-		seq++;
-		break;
-	    case 'L':
-		seq++;
-		printf("Lost\n");
-		break;
-	    default:
-		printf("Unknown character '%c'\n", c);
-		break;
-	    }
-
-#ifdef REPORT
-	    if (toupper(c) != 'L') {
-		pjmedia_jbuf_get_state(jb, &state);
-		printf("seq=%d\t%c\tsize=%d\tprefetch=%d\n", 
-		       seq, toupper(c), state.size, state.prefetch);
-	    }
-#endif
 	}
+
+	/* Print JB states */
+	pjmedia_jbuf_get_state(jb, &state);
+	printf("------------------------------------------------------\n");
+	printf("Summary:\n");
+	printf("  size=%d prefetch=%d\n", state.size, state.prefetch);
+	printf("  delay (min/max/avg/dev)=%d/%d/%d/%d ms\n",
+	       state.min_delay, state.max_delay, state.avg_delay, 
+	       state.dev_delay);
+	printf("  lost=%d discard=%d empty=%d burst(avg)=%d\n", 
+	       state.lost, state.discard, state.empty, state.avg_burst);
+
+	/* Evaluate test session */
+	if (cond.burst >= 0 && (int)state.avg_burst > cond.burst) {
+	    printf("! 'Burst' should be %d, it is %d\n", 
+		   cond.burst, state.avg_burst);
+	    rc |= 1;
+	}
+	if (cond.delay >= 0 && (int)state.avg_delay/JB_PTIME > cond.delay) {
+	    printf("! 'Delay' should be %d, it is %d\n", 
+		   cond.delay, state.avg_delay/JB_PTIME);
+	    rc |= 2;
+	}
+	if (cond.discard >= 0 && (int)state.discard > cond.discard) {
+	    printf("! 'Discard' should be %d, it is %d\n",
+		   cond.discard, state.discard);
+	    rc |= 4;
+	}
+	if (cond.empty >= 0 && (int)state.empty > cond.empty) {
+	    printf("! 'Empty' should be %d, it is %d\n", 
+		   cond.empty, state.empty);
+	    rc |= 8;
+	}
+	if (cond.lost >= 0 && (int)state.lost > cond.lost) {
+	    printf("! 'Lost' should be %d, it is %d\n", 
+		   cond.lost, state.lost);
+	    rc |= 16;
+	}
+
+	pjmedia_jbuf_destroy(jb);
+	pj_pool_release(pool);
     }
 
-    pjmedia_jbuf_destroy(jb);
+    fclose(input);
+    pj_log_set_level(old_log_level);
 
-    if (input != stdin)
-	fclose(input);
-
-    pj_pool_release(pool);
-    return 0;
+    return rc;
 }