apr-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From r..@apache.org
Subject cvs commit: apr/test Makefile.in test_apr.h testall.c testatomic.c
Date Sat, 13 Mar 2004 00:28:45 GMT
rbb         2004/03/12 16:28:45

  Modified:    test     Makefile.in test_apr.h testall.c testatomic.c
  Log:
  Migrate testatomic to testall.  I have commented out a test that
  specifically states that we expect it to fail.  We aren't actually testing
  APR with that test.
  
  Revision  Changes    Path
  1.148     +2 -5      apr/test/Makefile.in
  
  Index: Makefile.in
  ===================================================================
  RCS file: /home/cvs/apr/test/Makefile.in,v
  retrieving revision 1.147
  retrieving revision 1.148
  diff -u -r1.147 -r1.148
  --- Makefile.in	5 Mar 2004 19:52:06 -0000	1.147
  +++ Makefile.in	13 Mar 2004 00:28:45 -0000	1.148
  @@ -16,7 +16,6 @@
   	testlockperf@EXEEXT@ \
   	testshmproducer@EXEEXT@ \
   	testshmconsumer@EXEEXT@ \
  -	testatomic@EXEEXT@ \
   	testmutexscope@EXEEXT@ \
   	testall@EXEEXT@
   
  @@ -108,9 +107,6 @@
   testglobalmutex@EXEEXT@: testglobalmutex.lo $(LOCAL_LIBS)
   	$(LINK_PROG) testglobalmutex.lo $(LOCAL_LIBS) $(ALL_LIBS)
   
  -testatomic@EXEEXT@: testatomic.lo $(LOCAL_LIBS)
  -	$(LINK_PROG) testatomic.lo $(LOCAL_LIBS) $(ALL_LIBS)
  -
   testmutexscope@EXEEXT@: testmutexscope.lo $(LOCAL_LIBS)
   	$(LINK_PROG) testmutexscope.lo $(LOCAL_LIBS) $(ALL_LIBS)
   
  @@ -120,7 +116,8 @@
   	testdso.lo testoc.lo testdup.lo testsockets.lo testproc.lo \
   	testpoll.lo testlock.lo testsockopt.lo testpipe.lo testthread.lo \
   	testhash.lo testargs.lo testnames.lo testuser.lo testpath.lo \
  -	testenv.lo testprocmutex.lo testrand2.lo testfnmatch.lo
  +	testenv.lo testprocmutex.lo testrand2.lo testfnmatch.lo \
  +        testatomic.lo
   
   testall: $(TESTS) mod_test.la libmod_test.la occhild@EXEEXT@ \
   	 readchild@EXEEXT@ CuTest.lo proc_child@EXEEXT@ $(LOCAL_LIBS)
  
  
  
  1.46      +1 -0      apr/test/test_apr.h
  
  Index: test_apr.h
  ===================================================================
  RCS file: /home/cvs/apr/test/test_apr.h,v
  retrieving revision 1.45
  retrieving revision 1.46
  diff -u -r1.45 -r1.46
  --- test_apr.h	5 Mar 2004 19:52:06 -0000	1.45
  +++ test_apr.h	13 Mar 2004 00:28:45 -0000	1.46
  @@ -60,6 +60,7 @@
   CuSuite *testpath(void);
   CuSuite *testenv(void);
   CuSuite *testfnmatch(void);
  +CuSuite *testatomic(void);
   
   /* Assert that RV is an APR_SUCCESS value; else fail giving strerror
    * for RV and CONTEXT message. */
  
  
  
  1.51      +1 -0      apr/test/testall.c
  
  Index: testall.c
  ===================================================================
  RCS file: /home/cvs/apr/test/testall.c,v
  retrieving revision 1.50
  retrieving revision 1.51
  diff -u -r1.50 -r1.51
  --- testall.c	5 Mar 2004 19:52:06 -0000	1.50
  +++ testall.c	13 Mar 2004 00:28:45 -0000	1.51
  @@ -71,6 +71,7 @@
       {"testpath", testpath},
       {"testenv", testenv},
       {"testfnmatch", testfnmatch},
  +    {"testatomic", testatomic},
       {"LastTest", NULL}
   };
   
  
  
  
  1.40      +170 -250  apr/test/testatomic.c
  
  Index: testatomic.c
  ===================================================================
  RCS file: /home/cvs/apr/test/testatomic.c,v
  retrieving revision 1.39
  retrieving revision 1.40
  diff -u -r1.39 -r1.40
  --- testatomic.c	13 Feb 2004 09:38:34 -0000	1.39
  +++ testatomic.c	13 Mar 2004 00:28:45 -0000	1.40
  @@ -13,17 +13,13 @@
    * limitations under the License.
    */
   
  -#include <stdio.h>
  -#include <stdlib.h>
  +#include "test_apr.h"
  +#include "apr_strings.h"
   #include "apr_thread_proc.h"
   #include "apr_errno.h"
   #include "apr_general.h"
   #include "apr_atomic.h"
  -#include "errno.h"
   #include "apr_time.h"
  -#if APR_HAVE_UNISTD_H
  -#include <unistd.h>
  -#endif
   
   /* Use pthread_setconcurrency where it is available and not a nullop,
    * i.e. platforms using M:N or M:1 thread models: */
  @@ -37,179 +33,159 @@
   #include <pthread.h>
   #endif
   
  -apr_pool_t *context;
  -apr_uint32_t y;      /* atomic locks */
  -apr_uint32_t y32;
  +static void test_init(CuTest *tc)
  +{
  +    apr_assert_success(tc, "Could not initliaze atomics", apr_atomic_init(p));
  +}
   
  -static apr_status_t check_basic_atomics32(void)
  +static void test_set32(CuTest *tc)
   {
  -    apr_uint32_t oldval;
  -    apr_uint32_t casval = 0;
  -    apr_uint32_t minus1 = -1;
  +    apr_uint32_t y32;
  +    apr_atomic_set32(&y32, 2);
  +    CuAssertIntEquals(tc, 2, y32);
  +}
  +
  +static void test_read32(CuTest *tc)
  +{
  +    apr_uint32_t y32;
  +    apr_atomic_set32(&y32, 2);
  +    CuAssertIntEquals(tc, 2, apr_atomic_read32(&y32));
  +}
  +
  +static void test_dec32(CuTest *tc)
  +{
  +    apr_uint32_t y32;
  +    int rv;
   
       apr_atomic_set32(&y32, 2);
  -    printf("%-60s", "testing apr_atomic_dec32");
  -    if (apr_atomic_dec32(&y32) == 0) {
  -        fprintf(stderr, "Failed\noldval =%d should not be zero\n",
  -                apr_atomic_read32(&y32));
  -        return APR_EGENERAL;
  -    }
  -    if (apr_atomic_dec32(&y32) != 0) {
  -        fprintf(stderr, "Failed\noldval =%d should be zero\n",
  -                apr_atomic_read32(&y32));
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
   
  -    printf("%-60s", "testing apr_atomic_xchg32");
  +    rv = apr_atomic_dec32(&y32);
  +    CuAssertIntEquals(tc, 1, y32);
  +    CuAssert(tc, "atomic_dec returned zero when it shouldn't", rv != 0);
  +
  +    rv = apr_atomic_dec32(&y32);
  +    CuAssertIntEquals(tc, 0, y32);
  +    CuAssert(tc, "atomic_dec didn't returned zero when it should", rv == 0);
  +}
  +
  +static void test_xchg32(CuTest *tc)
  +{
  +    apr_uint32_t oldval;
  +    apr_uint32_t y32;
  +
       apr_atomic_set32(&y32, 100);
       oldval = apr_atomic_xchg32(&y32, 50);
  -    if (oldval != 100) {
  -        fprintf(stderr, "Failed\noldval =%d should be 100\n", oldval);
  -        return APR_EGENERAL;
  -    }
  -    if (y32 != 50) {
  -        fprintf(stderr, "Failed\nnewval =%d should be 50\n", oldval);
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
   
  -    printf("%-60s", "testing apr_atomic_cas32");
  +    CuAssertIntEquals(tc, 100, oldval);
  +    CuAssertIntEquals(tc, 50, y32);
  +}
  +
  +static void test_cas_equal(CuTest *tc)
  +{
  +    apr_uint32_t casval = 0;
  +    apr_uint32_t oldval;
  +
       oldval = apr_atomic_cas32(&casval, 12, 0);
  -    if (oldval != 0) {
  -        fprintf(stderr, "Failed\noldval =%d should be zero\n", oldval);
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
  -    printf("%-60s", "testing apr_atomic_cas32 - match non-null");
  -    oldval = apr_atomic_cas32(&casval, 23, 12);
  -    if (oldval != 12) {
  -        fprintf(stderr, "Failed\noldval =%d should be 12 y=%d\n",
  -                oldval, casval);
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
  -    printf("%-60s", "testing apr_atomic_cas32 - no match");
  +    CuAssertIntEquals(tc, 0, oldval);
  +    CuAssertIntEquals(tc, 12, casval);
  +}
  +
  +static void test_cas_equal_nonnull(CuTest *tc)
  +{
  +    apr_uint32_t casval = 12;
  +    apr_uint32_t oldval;
  +
       oldval = apr_atomic_cas32(&casval, 23, 12);
  -    if (oldval != 23) {
  -        fprintf(stderr, "Failed\noldval =%d should be 23 y=%d\n",
  -                oldval, casval);
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
  +    CuAssertIntEquals(tc, 12, oldval);
  +    CuAssertIntEquals(tc, 23, casval);
  +}
  +
  +static void test_cas_notequal(CuTest *tc)
  +{
  +    apr_uint32_t casval = 12;
  +    apr_uint32_t oldval;
  +
  +    oldval = apr_atomic_cas32(&casval, 23, 2);
  +    CuAssertIntEquals(tc, 12, oldval);
  +    CuAssertIntEquals(tc, 12, casval);
  +}
  +
  +static void test_add32(CuTest *tc)
  +{
  +    apr_uint32_t oldval;
  +    apr_uint32_t y32;
   
  -    printf("%-60s", "testing apr_atomic_add32");
       apr_atomic_set32(&y32, 23);
  -    if ((oldval = apr_atomic_add32(&y32, 4)) != 23) {
  -        fprintf(stderr,
  -                "Failed\noldval problem =%d should be 23\n",
  -                oldval);
  -        return APR_EGENERAL;
  -    }
  -    if ((oldval = apr_atomic_read32(&y32)) != 27) {
  -        fprintf(stderr,
  -                "Failed\nAtomic Add doesn't add up ;( expected 27 got %d\n",
  -                oldval);
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
  +    oldval = apr_atomic_add32(&y32, 4);
  +    CuAssertIntEquals(tc, 23, oldval);
  +    CuAssertIntEquals(tc, 27, y32);
  +}
   
  -    printf("%-60s", "testing apr_atomic_inc32");
  -    if ((oldval = apr_atomic_inc32(&y32)) != 27) {
  -        fprintf(stderr,
  -                "Failed\noldval problem =%d should be 27\n",
  -                oldval);
  -        return APR_EGENERAL;
  -    }
  -    if ((oldval = apr_atomic_read32(&y32)) != 28) {
  -        fprintf(stderr,
  -                "Failed\nAtomic Inc didn't increment ;( expected 28 got %d\n",
  -                oldval);
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
  +static void test_inc32(CuTest *tc)
  +{
  +    apr_uint32_t oldval;
  +    apr_uint32_t y32;
  +
  +    apr_atomic_set32(&y32, 23);
  +    oldval = apr_atomic_inc32(&y32);
  +    CuAssertIntEquals(tc, 23, oldval);
  +    CuAssertIntEquals(tc, 24, y32);
  +}
  +
  +static void test_set_add_inc_sub(CuTest *tc)
  +{
  +    apr_uint32_t y32;
   
  -    printf("%-60s", "testing add32/inc32/sub32");
       apr_atomic_set32(&y32, 0);
       apr_atomic_add32(&y32, 20);
       apr_atomic_inc32(&y32);
       apr_atomic_sub32(&y32, 10);
  -    if (apr_atomic_read32(&y32) != 11) {
  -        fprintf(stderr, "Failed.\natomics do not add up: expected 11 got %d\n",
  -		apr_atomic_read32(&y32));
  -        return APR_EGENERAL;
  -    }
  -    fprintf(stdout, "OK\n");
   
  -    printf("%-60s", "testing wrapping around zero");
  +    CuAssertIntEquals(tc, 11, y32);
  +}
   
  -    apr_atomic_set32(&y32, 0);
  -    if (apr_atomic_dec32(&y32) == 0) {
  -        fprintf(stderr, "apr_atomic_dec32 on zero returned zero.\n");
  -        return APR_EGENERAL;
  -    }
  -    if (apr_atomic_read32(&y32) != minus1) {
  -        fprintf(stderr, "zero wrap failed: 0 - 1 = %d\n",
  -                apr_atomic_read32(&y32));
  -        return APR_EGENERAL;
  -    }
  +static void test_wrap_zero(CuTest *tc)
  +{
  +    apr_uint32_t y32;
  +    apr_uint32_t rv;
  +    apr_uint32_t minus1 = -1;
   
  -    if (apr_atomic_inc32(&y32) != minus1) {
  -        fprintf(stderr, "apr_atomic_inc32 on -1 returned bad value.\n");
  -        return APR_EGENERAL;
  -    }
  -    if (apr_atomic_read32(&y32) != 0) {
  -        fprintf(stderr, "zero wrap failed: -1 + 1 = %u\n", 
  -                apr_atomic_read32(&y32));
  -        return APR_EGENERAL;
  -    }
  -    printf("OK\n");
  +    apr_atomic_set32(&y32, 0);
  +    rv = apr_atomic_dec32(&y32);
   
  -    return APR_SUCCESS;
  +    CuAssert(tc, "apr_atomic_dec32 on zero returned zero.", rv != 0);
  +    char *str = apr_psprintf(p, "zero wrap failed: 0 - 1 = %d", y32);
  +    CuAssert(tc, str, y32 == minus1);
   }
   
  -#if !APR_HAS_THREADS
  -int main(void)
  +static void test_inc_neg1(CuTest *tc)
   {
  -    apr_status_t rv;
  -
  -    apr_initialize();
  +    apr_uint32_t y32 = -1;
  +    apr_uint32_t minus1 = -1;
  +    apr_uint32_t rv;
   
  -    fprintf(stderr,
  -            "This program won't work fully on this platform because there is no "
  -            "support for threads.\n");
  -    if (apr_pool_create(&context, NULL) != APR_SUCCESS) {
  -        fflush(stdout);
  -        fprintf(stderr, "Failed.\nCould not initialize\n");
  -        exit(-1);
  -    }
  -    rv = apr_atomic_init(context);
  -    if (rv != APR_SUCCESS) {
  -        fprintf(stderr, "Failed.\nCould not initialize atomics\n");
  -        exit(-1);
  -    }
  +    rv = apr_atomic_inc32(&y32);
   
  -    rv = check_basic_atomics32();
  -    if (rv != APR_SUCCESS) {
  -        fprintf(stderr, "Failed.\n");
  -        exit(-1);
  -    }
  -    return 0;
  +    CuAssert(tc, "apr_atomic_dec32 on zero returned zero.", rv == minus1);
  +    char *str = apr_psprintf(p, "zero wrap failed: -1 + 1 = %d", y32);
  +    CuAssert(tc, str, y32 == 0);
   }
  -#else /* !APR_HAS_THREADS */
  +
  +
  +#if APR_HAS_THREADS
   
   void * APR_THREAD_FUNC thread_func_mutex(apr_thread_t *thd, void *data);
   void * APR_THREAD_FUNC thread_func_atomic(apr_thread_t *thd, void *data);
   void * APR_THREAD_FUNC thread_func_none(apr_thread_t *thd, void *data);
   
   apr_thread_mutex_t *thread_lock;
  -volatile long x = 0; /* mutex locks */
  -volatile long z = 0; /* no locks */
  -int value = 0;
  +volatile apr_uint32_t x = 0; /* mutex locks */
  +volatile apr_uint32_t y = 0; /* atomic operations */
  +volatile apr_uint32_t z = 0; /* no locks */
   apr_status_t exit_ret_val = 123; /* just some made up number to check on later */
   
  -#define NUM_THREADS 20
  -#define NUM_ITERATIONS 200000
  +#define NUM_THREADS 40
  +#define NUM_ITERATIONS 20000
   void * APR_THREAD_FUNC thread_func_mutex(apr_thread_t *thd, void *data)
   {
       int i;
  @@ -248,136 +224,80 @@
       return NULL;
   }
   
  -int main(int argc, char**argv)
  +static void test_atomics_threaded(CuTest *tc)
   {
       apr_thread_t *t1[NUM_THREADS];
       apr_thread_t *t2[NUM_THREADS];
  +    apr_thread_t *t3[NUM_THREADS];
       apr_status_t r1[NUM_THREADS]; 
       apr_status_t r2[NUM_THREADS]; 
  +    apr_status_t r3[NUM_THREADS]; 
       apr_status_t s1[NUM_THREADS]; 
       apr_status_t s2[NUM_THREADS];
  +    apr_status_t s3[NUM_THREADS];
       apr_status_t rv;
       int i;
  -    int mutex;
  -
  -    apr_initialize();
   
  -    if (argc == 2 && argv[1][0] == 'm') {
  -        mutex = 1;
  -    }
  -    else {
  -        mutex = 0;
  -    }
  -
  -    printf("APR Atomic Test\n===============\n\n");
   #ifdef HAVE_PTHREAD_SETCONCURRENCY
       pthread_setconcurrency(8);
   #endif
  -    printf("%-60s", "Initializing the context"); 
  -    if (apr_pool_create(&context, NULL) != APR_SUCCESS) {
  -        fflush(stdout);
  -        fprintf(stderr, "Failed.\nCould not initialize\n");
  -        exit(-1);
  -    }
  -    printf("OK\n");
   
  -    if (mutex == 1) {
  -        printf("%-60s", "Initializing the lock"); 
  -        rv = apr_thread_mutex_create(&thread_lock, APR_THREAD_MUTEX_DEFAULT,
  -                                     context);
  -        if (rv != APR_SUCCESS) {
  -            fflush(stdout);
  -            fprintf(stderr, "Failed\nCould not create lock\n");
  -            exit(-1);
  -        }
  -        printf("OK\n");
  -    }
  -    printf("%-60s", "Initializing the atomics"); 
  -    rv = apr_atomic_init(context);
  -    if (rv != APR_SUCCESS) {
  -        fprintf(stderr, "Failed.\n");
  -        exit(-1);
  -    }
  -    printf("OK\n");
  -
  -    rv = check_basic_atomics32();
  -    if (rv != APR_SUCCESS) {
  -        fprintf(stderr, "Failed.\n");
  -        exit(-1);
  -    }
  +    rv = apr_thread_mutex_create(&thread_lock, APR_THREAD_MUTEX_DEFAULT, p);
  +    apr_assert_success(tc, "Could not create lock", rv);
   
  -
  -    printf("%-60s", "Starting all the threads"); 
       for (i = 0; i < NUM_THREADS; i++) {
  -        r1[i] = apr_thread_create(&t1[i], NULL, 
  -                    (mutex == 1 ? thread_func_mutex : thread_func_atomic),
  -                    NULL, context);
  -        r2[i] = apr_thread_create(&t2[i], NULL, thread_func_none, NULL,
  -                                  context);
  -        if (r1[i] != APR_SUCCESS || r2[i] != APR_SUCCESS ) {
  -            fflush(stdout);
  -            fprintf(stderr, "Failed\nError starting thread in group %d\n",i);
  -            exit(-1);
  -        }
  +        r1[i] = apr_thread_create(&t1[i], NULL, thread_func_mutex, NULL, p);
  +        r2[i] = apr_thread_create(&t2[i], NULL, thread_func_atomic, NULL, p);
  +        r3[i] = apr_thread_create(&t3[i], NULL, thread_func_none, NULL, p);
  +        CuAssert(tc, "Failed creating threads",
  +                 r1[i] == APR_SUCCESS && r2[i] == APR_SUCCESS && 
  +                 r3[i] == APR_SUCCESS);
       }
  -    printf("OK\n");
  -
  -    printf("%-60s\n", "Waiting for threads to exit");
  -    printf("%-60s", "(Note that this may take a while to complete.)");
  -    fflush(stdout);
   
       for (i = 0; i < NUM_THREADS; i++) {
           apr_thread_join(&s1[i], t1[i]);
           apr_thread_join(&s2[i], t2[i]);
  -        if (s1[i] != exit_ret_val || s2[i] != exit_ret_val) {
  -            fprintf(stderr, 
  -                    "Invalid return value\n"
  -                    "Got %d/%d, but expected %d for all \n",
  -                    s1[i], s2[i], exit_ret_val);
  -        }
  +        apr_thread_join(&s3[i], t3[i]);
  +        CuAssert(tc, "Invalid return value from thread_join",
  +                 s1[i] == exit_ret_val && s2[i] == exit_ret_val && 
  +                 s3[i] == exit_ret_val);
       }
  -    printf("OK\n");
   
  -    if (mutex == 1) {
  -        printf("%-60s", "Checking if mutex locks worked"); 
  -        if (x != NUM_THREADS * NUM_ITERATIONS) {
  -            fflush(stdout);
  -            fprintf(stderr, 
  -                    "No!\nThe locks didn't work?? x = %ld instead of %ld\n",
  -                    x,
  -                    (long)NUM_THREADS * NUM_ITERATIONS);
  -        }
  -        else {
  -            printf("OK\n");
  -        }
  -    }
  -    else {
  -        printf("%-60s", "Checking if atomic worked"); 
  -        if (apr_atomic_read32(&y) != NUM_THREADS * NUM_ITERATIONS) {
  -            fflush(stdout);
  -            fprintf(stderr, 
  -                    "No!\nThe atomics didn't work?? y = %ld instead of %ld\n",
  -                    (long)apr_atomic_read32(&y),
  -                    (long)NUM_THREADS * NUM_ITERATIONS);
  -        }
  -        else {
  -            printf("OK\n");
  -        }
  -    }
  -    printf("%-60s", "Checking if nolock worked"); 
  -    if (z != NUM_THREADS * NUM_ITERATIONS) {
  -        fflush(stdout);
  -        fprintf(stderr, 
  -                "no surprise\n"
  -                "The no-locks didn't work. z = %ld instead of %ld\n", 
  -                z, 
  -                (long)NUM_THREADS * NUM_ITERATIONS);
  -    }
  -    else {
  -        printf("OK\n");
  -    }
  -
  -    return 0;
  +    CuAssertIntEquals(tc, x, NUM_THREADS * NUM_ITERATIONS);
  +    CuAssertIntEquals(tc, apr_atomic_read32(&y), NUM_THREADS * NUM_ITERATIONS);
  +    /* Comment out this test, because I have no clue what this test is
  +     * actually telling us.  We are checking something that may or may not
  +     * be true, and it isn't really testing APR at all.
  +    CuAssert(tc, "We expect this to fail, because we tried to update "
  +                 "an integer in a non-thread-safe manner.",
  +             z != NUM_THREADS * NUM_ITERATIONS);
  +     */
   }
   
   #endif /* !APR_HAS_THREADS */
  +
  +CuSuite *testatomic(void)
  +{
  +    CuSuite *suite = CuSuiteNew("Atomic");
  +
  +    SUITE_ADD_TEST(suite, test_init);
  +    SUITE_ADD_TEST(suite, test_set32);
  +    SUITE_ADD_TEST(suite, test_read32);
  +    SUITE_ADD_TEST(suite, test_dec32);
  +    SUITE_ADD_TEST(suite, test_xchg32);
  +    SUITE_ADD_TEST(suite, test_cas_equal);
  +    SUITE_ADD_TEST(suite, test_cas_equal_nonnull);
  +    SUITE_ADD_TEST(suite, test_cas_notequal);
  +    SUITE_ADD_TEST(suite, test_add32);
  +    SUITE_ADD_TEST(suite, test_inc32);
  +    SUITE_ADD_TEST(suite, test_set_add_inc_sub);
  +    SUITE_ADD_TEST(suite, test_wrap_zero);
  +    SUITE_ADD_TEST(suite, test_inc_neg1);
  +
  +#if APR_HAS_THREADS
  +    SUITE_ADD_TEST(suite, test_atomics_threaded);
  +#endif
  +
  +    return suite;
  +}
  +
  
  
  

Mime
View raw message