зеркало из https://github.com/mozilla/pjs.git
544 строки
17 KiB
C
544 строки
17 KiB
C
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/*
|
|
* The contents of this file are subject to the Mozilla Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is the Netscape security libraries.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998-2000 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the
|
|
* terms of the GNU General Public License Version 2 or later (the
|
|
* "GPL"), in which case the provisions of the GPL are applicable
|
|
* instead of those above. If you wish to allow use of your
|
|
* version of this file only under the terms of the GPL and not to
|
|
* allow others to use your version of this file under the MPL,
|
|
* indicate your decision by deleting the provisions above and
|
|
* replace them with the notice and other provisions required by
|
|
* the GPL. If you do not delete the provisions above, a recipient
|
|
* may use your version of this file under either the MPL or the
|
|
* GPL.
|
|
*/
|
|
|
|
/*
|
|
* Test: y2ktmo
|
|
*
|
|
* Description:
|
|
* This test tests the interval time facilities in NSPR for Y2K
|
|
* compliance. All the functions that take a timeout argument
|
|
* are tested: PR_Sleep, socket I/O (PR_Accept is taken as a
|
|
* representative), PR_Poll, PR_WaitCondVar, PR_Wait, and
|
|
* PR_CWait. A thread of each thread scope (local, global, and
|
|
* global bound) is created to call each of these functions.
|
|
* The test should be started at the specified number of seconds
|
|
* (called the lead time) before a Y2K rollover test date. The
|
|
* timeout values for these threads will span over the rollover
|
|
* date by at least the specified number of seconds. For
|
|
* example, if the lead time is 5 seconds, the test should
|
|
* be started at time (D - 5), where D is a rollover date, and
|
|
* the threads will time out at or after time (D + 5). The
|
|
* timeout values for the threads are spaced one second apart.
|
|
*
|
|
* When a thread times out, it calls PR_IntervalNow() to verify
|
|
* that it did wait for the specified time. In addition, it
|
|
* calls a platform-native function to verify the actual elapsed
|
|
* time again, to rule out the possibility that PR_IntervalNow()
|
|
* is broken. We allow the actual elapsed time to deviate from
|
|
* the specified timeout by a certain tolerance (in milliseconds).
|
|
*/
|
|
|
|
#include "nspr.h"
|
|
#include "plgetopt.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#if defined(XP_UNIX)
|
|
#include <sys/time.h> /* for gettimeofday */
|
|
#endif
|
|
#if defined(WIN32)
|
|
#include <sys/types.h>
|
|
#include <sys/timeb.h> /* for _ftime */
|
|
#endif
|
|
|
|
#define DEFAULT_LEAD_TIME_SECS 5
|
|
#define DEFAULT_TOLERANCE_MSECS 500
|
|
|
|
static PRBool debug_mode = PR_FALSE;
|
|
static PRInt32 lead_time_secs = DEFAULT_LEAD_TIME_SECS;
|
|
static PRInt32 tolerance_msecs = DEFAULT_TOLERANCE_MSECS;
|
|
static PRIntervalTime start_time;
|
|
static PRIntervalTime tolerance;
|
|
|
|
#if defined(XP_UNIX)
|
|
static struct timeval start_time_tv;
|
|
#endif
|
|
#if defined(WIN32)
|
|
static struct _timeb start_time_tb;
|
|
#endif
|
|
|
|
static void SleepThread(void *arg)
|
|
{
|
|
PRIntervalTime timeout = (PRIntervalTime) arg;
|
|
PRIntervalTime elapsed;
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
PRInt32 timeout_msecs = PR_IntervalToMilliseconds(timeout);
|
|
PRInt32 elapsed_msecs;
|
|
#endif
|
|
#if defined(XP_UNIX)
|
|
struct timeval end_time_tv;
|
|
#endif
|
|
#if defined(WIN32)
|
|
struct _timeb end_time_tb;
|
|
#endif
|
|
|
|
if (PR_Sleep(timeout) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_Sleep failed\n");
|
|
exit(1);
|
|
}
|
|
elapsed = (PRIntervalTime)(PR_IntervalNow() - start_time);
|
|
if (elapsed + tolerance < timeout || elapsed > timeout + tolerance) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#if defined(XP_UNIX)
|
|
gettimeofday(&end_time_tv, NULL);
|
|
elapsed_msecs = 1000*(end_time_tv.tv_sec - start_time_tv.tv_sec)
|
|
+ (end_time_tv.tv_usec - start_time_tv.tv_usec)/1000;
|
|
#endif
|
|
#if defined(WIN32)
|
|
_ftime(&end_time_tb);
|
|
elapsed_msecs = 1000*(end_time_tb.time - start_time_tb.time)
|
|
+ (end_time_tb.millitm - start_time_tb.millitm);
|
|
#endif
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
if (elapsed_msecs + tolerance_msecs < timeout_msecs
|
|
|| elapsed_msecs > timeout_msecs + tolerance_msecs) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
if (debug_mode) {
|
|
fprintf(stderr, "Sleep thread (scope %d) done\n",
|
|
PR_GetThreadScope(PR_GetCurrentThread()));
|
|
}
|
|
}
|
|
|
|
static void AcceptThread(void *arg)
|
|
{
|
|
PRIntervalTime timeout = (PRIntervalTime) arg;
|
|
PRIntervalTime elapsed;
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
PRInt32 timeout_msecs = PR_IntervalToMilliseconds(timeout);
|
|
PRInt32 elapsed_msecs;
|
|
#endif
|
|
#if defined(XP_UNIX)
|
|
struct timeval end_time_tv;
|
|
#endif
|
|
#if defined(WIN32)
|
|
struct _timeb end_time_tb;
|
|
#endif
|
|
PRFileDesc *sock;
|
|
PRNetAddr addr;
|
|
PRFileDesc *accepted;
|
|
|
|
sock = PR_NewTCPSocket();
|
|
if (sock == NULL) {
|
|
fprintf(stderr, "PR_NewTCPSocket failed\n");
|
|
exit(1);
|
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.inet.family = PR_AF_INET;
|
|
addr.inet.port = 0;
|
|
addr.inet.ip = PR_htonl(PR_INADDR_ANY);
|
|
if (PR_Bind(sock, &addr) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_Bind failed\n");
|
|
exit(1);
|
|
}
|
|
if (PR_Listen(sock, 5) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_Listen failed\n");
|
|
exit(1);
|
|
}
|
|
accepted = PR_Accept(sock, NULL, timeout);
|
|
if (accepted != NULL || PR_GetError() != PR_IO_TIMEOUT_ERROR) {
|
|
fprintf(stderr, "PR_Accept did not time out\n");
|
|
exit(1);
|
|
}
|
|
elapsed = (PRIntervalTime)(PR_IntervalNow() - start_time);
|
|
if (elapsed + tolerance < timeout || elapsed > timeout + tolerance) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#if defined(XP_UNIX)
|
|
gettimeofday(&end_time_tv, NULL);
|
|
elapsed_msecs = 1000*(end_time_tv.tv_sec - start_time_tv.tv_sec)
|
|
+ (end_time_tv.tv_usec - start_time_tv.tv_usec)/1000;
|
|
#endif
|
|
#if defined(WIN32)
|
|
_ftime(&end_time_tb);
|
|
elapsed_msecs = 1000*(end_time_tb.time - start_time_tb.time)
|
|
+ (end_time_tb.millitm - start_time_tb.millitm);
|
|
#endif
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
if (elapsed_msecs + tolerance_msecs < timeout_msecs
|
|
|| elapsed_msecs > timeout_msecs + tolerance_msecs) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
if (PR_Close(sock) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_Close failed\n");
|
|
exit(1);
|
|
}
|
|
if (debug_mode) {
|
|
fprintf(stderr, "Accept thread (scope %d) done\n",
|
|
PR_GetThreadScope(PR_GetCurrentThread()));
|
|
}
|
|
}
|
|
|
|
static void PollThread(void *arg)
|
|
{
|
|
PRIntervalTime timeout = (PRIntervalTime) arg;
|
|
PRIntervalTime elapsed;
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
PRInt32 timeout_msecs = PR_IntervalToMilliseconds(timeout);
|
|
PRInt32 elapsed_msecs;
|
|
#endif
|
|
#if defined(XP_UNIX)
|
|
struct timeval end_time_tv;
|
|
#endif
|
|
#if defined(WIN32)
|
|
struct _timeb end_time_tb;
|
|
#endif
|
|
PRFileDesc *sock;
|
|
PRNetAddr addr;
|
|
PRPollDesc pd;
|
|
PRIntn rv;
|
|
|
|
sock = PR_NewTCPSocket();
|
|
if (sock == NULL) {
|
|
fprintf(stderr, "PR_NewTCPSocket failed\n");
|
|
exit(1);
|
|
}
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.inet.family = PR_AF_INET;
|
|
addr.inet.port = 0;
|
|
addr.inet.ip = PR_htonl(PR_INADDR_ANY);
|
|
if (PR_Bind(sock, &addr) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_Bind failed\n");
|
|
exit(1);
|
|
}
|
|
if (PR_Listen(sock, 5) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_Listen failed\n");
|
|
exit(1);
|
|
}
|
|
pd.fd = sock;
|
|
pd.in_flags = PR_POLL_READ;
|
|
rv = PR_Poll(&pd, 1, timeout);
|
|
if (rv != 0) {
|
|
fprintf(stderr, "PR_Poll did not time out\n");
|
|
exit(1);
|
|
}
|
|
elapsed = (PRIntervalTime)(PR_IntervalNow() - start_time);
|
|
if (elapsed + tolerance < timeout || elapsed > timeout + tolerance) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#if defined(XP_UNIX)
|
|
gettimeofday(&end_time_tv, NULL);
|
|
elapsed_msecs = 1000*(end_time_tv.tv_sec - start_time_tv.tv_sec)
|
|
+ (end_time_tv.tv_usec - start_time_tv.tv_usec)/1000;
|
|
#endif
|
|
#if defined(WIN32)
|
|
_ftime(&end_time_tb);
|
|
elapsed_msecs = 1000*(end_time_tb.time - start_time_tb.time)
|
|
+ (end_time_tb.millitm - start_time_tb.millitm);
|
|
#endif
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
if (elapsed_msecs + tolerance_msecs < timeout_msecs
|
|
|| elapsed_msecs > timeout_msecs + tolerance_msecs) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
if (PR_Close(sock) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_Close failed\n");
|
|
exit(1);
|
|
}
|
|
if (debug_mode) {
|
|
fprintf(stderr, "Poll thread (scope %d) done\n",
|
|
PR_GetThreadScope(PR_GetCurrentThread()));
|
|
}
|
|
}
|
|
|
|
static void WaitCondVarThread(void *arg)
|
|
{
|
|
PRIntervalTime timeout = (PRIntervalTime) arg;
|
|
PRIntervalTime elapsed;
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
PRInt32 timeout_msecs = PR_IntervalToMilliseconds(timeout);
|
|
PRInt32 elapsed_msecs;
|
|
#endif
|
|
#if defined(XP_UNIX)
|
|
struct timeval end_time_tv;
|
|
#endif
|
|
#if defined(WIN32)
|
|
struct _timeb end_time_tb;
|
|
#endif
|
|
PRLock *ml;
|
|
PRCondVar *cv;
|
|
|
|
ml = PR_NewLock();
|
|
if (ml == NULL) {
|
|
fprintf(stderr, "PR_NewLock failed\n");
|
|
exit(1);
|
|
}
|
|
cv = PR_NewCondVar(ml);
|
|
if (cv == NULL) {
|
|
fprintf(stderr, "PR_NewCondVar failed\n");
|
|
exit(1);
|
|
}
|
|
PR_Lock(ml);
|
|
PR_WaitCondVar(cv, timeout);
|
|
PR_Unlock(ml);
|
|
elapsed = (PRIntervalTime)(PR_IntervalNow() - start_time);
|
|
if (elapsed + tolerance < timeout || elapsed > timeout + tolerance) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#if defined(XP_UNIX)
|
|
gettimeofday(&end_time_tv, NULL);
|
|
elapsed_msecs = 1000*(end_time_tv.tv_sec - start_time_tv.tv_sec)
|
|
+ (end_time_tv.tv_usec - start_time_tv.tv_usec)/1000;
|
|
#endif
|
|
#if defined(WIN32)
|
|
_ftime(&end_time_tb);
|
|
elapsed_msecs = 1000*(end_time_tb.time - start_time_tb.time)
|
|
+ (end_time_tb.millitm - start_time_tb.millitm);
|
|
#endif
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
if (elapsed_msecs + tolerance_msecs < timeout_msecs
|
|
|| elapsed_msecs > timeout_msecs + tolerance_msecs) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
PR_DestroyCondVar(cv);
|
|
PR_DestroyLock(ml);
|
|
if (debug_mode) {
|
|
fprintf(stderr, "wait cond var thread (scope %d) done\n",
|
|
PR_GetThreadScope(PR_GetCurrentThread()));
|
|
}
|
|
}
|
|
|
|
static void WaitMonitorThread(void *arg)
|
|
{
|
|
PRIntervalTime timeout = (PRIntervalTime) arg;
|
|
PRIntervalTime elapsed;
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
PRInt32 timeout_msecs = PR_IntervalToMilliseconds(timeout);
|
|
PRInt32 elapsed_msecs;
|
|
#endif
|
|
#if defined(XP_UNIX)
|
|
struct timeval end_time_tv;
|
|
#endif
|
|
#if defined(WIN32)
|
|
struct _timeb end_time_tb;
|
|
#endif
|
|
PRMonitor *mon;
|
|
|
|
mon = PR_NewMonitor();
|
|
if (mon == NULL) {
|
|
fprintf(stderr, "PR_NewMonitor failed\n");
|
|
exit(1);
|
|
}
|
|
PR_EnterMonitor(mon);
|
|
PR_Wait(mon, timeout);
|
|
PR_ExitMonitor(mon);
|
|
elapsed = (PRIntervalTime)(PR_IntervalNow() - start_time);
|
|
if (elapsed + tolerance < timeout || elapsed > timeout + tolerance) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#if defined(XP_UNIX)
|
|
gettimeofday(&end_time_tv, NULL);
|
|
elapsed_msecs = 1000*(end_time_tv.tv_sec - start_time_tv.tv_sec)
|
|
+ (end_time_tv.tv_usec - start_time_tv.tv_usec)/1000;
|
|
#endif
|
|
#if defined(WIN32)
|
|
_ftime(&end_time_tb);
|
|
elapsed_msecs = 1000*(end_time_tb.time - start_time_tb.time)
|
|
+ (end_time_tb.millitm - start_time_tb.millitm);
|
|
#endif
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
if (elapsed_msecs + tolerance_msecs < timeout_msecs
|
|
|| elapsed_msecs > timeout_msecs + tolerance_msecs) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
PR_DestroyMonitor(mon);
|
|
if (debug_mode) {
|
|
fprintf(stderr, "wait monitor thread (scope %d) done\n",
|
|
PR_GetThreadScope(PR_GetCurrentThread()));
|
|
}
|
|
}
|
|
|
|
static void WaitCMonitorThread(void *arg)
|
|
{
|
|
PRIntervalTime timeout = (PRIntervalTime) arg;
|
|
PRIntervalTime elapsed;
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
PRInt32 timeout_msecs = PR_IntervalToMilliseconds(timeout);
|
|
PRInt32 elapsed_msecs;
|
|
#endif
|
|
#if defined(XP_UNIX)
|
|
struct timeval end_time_tv;
|
|
#endif
|
|
#if defined(WIN32)
|
|
struct _timeb end_time_tb;
|
|
#endif
|
|
int dummy;
|
|
|
|
PR_CEnterMonitor(&dummy);
|
|
PR_CWait(&dummy, timeout);
|
|
PR_CExitMonitor(&dummy);
|
|
elapsed = (PRIntervalTime)(PR_IntervalNow() - start_time);
|
|
if (elapsed + tolerance < timeout || elapsed > timeout + tolerance) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#if defined(XP_UNIX)
|
|
gettimeofday(&end_time_tv, NULL);
|
|
elapsed_msecs = 1000*(end_time_tv.tv_sec - start_time_tv.tv_sec)
|
|
+ (end_time_tv.tv_usec - start_time_tv.tv_usec)/1000;
|
|
#endif
|
|
#if defined(WIN32)
|
|
_ftime(&end_time_tb);
|
|
elapsed_msecs = 1000*(end_time_tb.time - start_time_tb.time)
|
|
+ (end_time_tb.millitm - start_time_tb.millitm);
|
|
#endif
|
|
#if defined(XP_UNIX) || defined(WIN32)
|
|
if (elapsed_msecs + tolerance_msecs < timeout_msecs
|
|
|| elapsed_msecs > timeout_msecs + tolerance_msecs) {
|
|
fprintf(stderr, "timeout wrong\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
if (debug_mode) {
|
|
fprintf(stderr, "wait cached monitor thread (scope %d) done\n",
|
|
PR_GetThreadScope(PR_GetCurrentThread()));
|
|
}
|
|
}
|
|
|
|
typedef void (*NSPRThreadFunc)(void*);
|
|
|
|
static NSPRThreadFunc threadFuncs[] = {
|
|
SleepThread, AcceptThread, PollThread,
|
|
WaitCondVarThread, WaitMonitorThread, WaitCMonitorThread};
|
|
|
|
static PRThreadScope threadScopes[] = {
|
|
PR_LOCAL_THREAD, PR_GLOBAL_THREAD, PR_GLOBAL_BOUND_THREAD};
|
|
|
|
static void Help(void)
|
|
{
|
|
fprintf(stderr, "y2ktmo test program usage:\n");
|
|
fprintf(stderr, "\t-d debug mode (FALSE)\n");
|
|
fprintf(stderr, "\t-l <secs> lead time (%d)\n",
|
|
DEFAULT_LEAD_TIME_SECS);
|
|
fprintf(stderr, "\t-t <msecs> tolerance (%d)\n",
|
|
DEFAULT_TOLERANCE_MSECS);
|
|
fprintf(stderr, "\t-h this message\n");
|
|
} /* Help */
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
PRThread **threads;
|
|
int num_thread_funcs = sizeof(threadFuncs)/sizeof(NSPRThreadFunc);
|
|
int num_thread_scopes = sizeof(threadScopes)/sizeof(PRThreadScope);
|
|
int i, j;
|
|
int idx;
|
|
PRInt32 secs;
|
|
PLOptStatus os;
|
|
PLOptState *opt = PL_CreateOptState(argc, argv, "dl:t:h");
|
|
|
|
while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
|
|
if (PL_OPT_BAD == os) continue;
|
|
switch (opt->option) {
|
|
case 'd': /* debug mode */
|
|
debug_mode = PR_TRUE;
|
|
break;
|
|
case 'l': /* lead time */
|
|
lead_time_secs = atoi(opt->value);
|
|
break;
|
|
case 't': /* tolerance */
|
|
tolerance_msecs = atoi(opt->value);
|
|
break;
|
|
case 'h':
|
|
default:
|
|
Help();
|
|
return 2;
|
|
}
|
|
}
|
|
PL_DestroyOptState(opt);
|
|
|
|
if (debug_mode) {
|
|
fprintf(stderr, "lead time: %d secs\n", lead_time_secs);
|
|
fprintf(stderr, "tolerance: %d msecs\n", tolerance_msecs);
|
|
}
|
|
|
|
start_time = PR_IntervalNow();
|
|
#if defined(XP_UNIX)
|
|
gettimeofday(&start_time_tv, NULL);
|
|
#endif
|
|
#if defined(WIN32)
|
|
_ftime(&start_time_tb);
|
|
#endif
|
|
tolerance = PR_MillisecondsToInterval(tolerance_msecs);
|
|
|
|
threads = PR_Malloc(
|
|
num_thread_scopes * num_thread_funcs * sizeof(PRThread*));
|
|
if (threads == NULL) {
|
|
fprintf(stderr, "PR_Malloc failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* start to time out 5 seconds after a rollover date */
|
|
secs = lead_time_secs + 5;
|
|
idx = 0;
|
|
for (i = 0; i < num_thread_scopes; i++) {
|
|
for (j = 0; j < num_thread_funcs; j++) {
|
|
threads[idx] = PR_CreateThread(PR_USER_THREAD, threadFuncs[j],
|
|
(void*)PR_SecondsToInterval(secs), PR_PRIORITY_NORMAL,
|
|
threadScopes[i], PR_JOINABLE_THREAD, 0);
|
|
if (threads[idx] == NULL) {
|
|
fprintf(stderr, "PR_CreateThread failed\n");
|
|
exit(1);
|
|
}
|
|
secs++;
|
|
idx++;
|
|
}
|
|
}
|
|
for (idx = 0; idx < num_thread_scopes*num_thread_funcs; idx++) {
|
|
if (PR_JoinThread(threads[idx]) == PR_FAILURE) {
|
|
fprintf(stderr, "PR_JoinThread failed\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
PR_Free(threads);
|
|
printf("PASS\n");
|
|
return 0;
|
|
}
|