db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d..@apache.org
Subject svn commit: r634425 - in /db/derby/code/trunk/java/testing/org/apache/derbyTesting: functionTests/tests/derbynet/ functionTests/tests/management/ junit/
Date Thu, 06 Mar 2008 21:30:50 GMT
Author: djd
Date: Thu Mar  6 13:30:48 2008
New Revision: 634425

URL: http://svn.apache.org/viewvc?rev=634425&view=rev
Log:
DERBY-3504 Fix timeout errors in management._Suite when running with classes. Was due to the
spawned vm to execute the server failing as installing the policy file requires jars. Changed
the decorator to add the -noSecurityManager flag if classes is being used with comments indicating
if tests need a different behaviour they need to provide it.
Added a SpawnedProcess utilitly class that correctly handles the output streams written by
a spawned process by having two background threads that read from the streams into a buffer.
This stops the change the process hangs due to being blocked writing stdout or stderr. Used
this utility class in one more location where a vm was being spawned. Ideally the spawning
of a java process should be in a single utility not scattered around multple tests, separate
cleanup issue. 

Added:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java   (with
props)
Modified:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SSLTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/ServerPropertiesTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/management/MBeanTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SSLTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SSLTest.java?rev=634425&r1=634424&r2=634425&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SSLTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SSLTest.java
Thu Mar  6 13:30:48 2008
@@ -54,15 +54,11 @@
 
 public class SSLTest extends BaseJDBCTestCase
 {
-    // helper state for intercepting server error messages
-    private InputStream[]  _inputStreamHolder;
-
     // Constructors
 
     public SSLTest(String testName)
     {
         super(testName);
-        _inputStreamHolder = new InputStream[1];
     }
     
     // JUnit machinery
@@ -86,17 +82,7 @@
         suite.addTest(decorateTest("testSSLBasicDSConnect"));
         suite.addTest(decorateTest("testSSLBasicDSPlainConnect"));
         return suite;
-    }
-    
-    /**
-     * Release resources.
-     */
-
-    protected void tearDown() throws Exception
-    {
-        _inputStreamHolder = null;
-    }
-    
+    }   
 
     // Test decoration
     
@@ -120,8 +106,7 @@
             new NetworkServerTestSetup(sslTest,
                                        startupProperties,
                                        startupArgs,
-                                       true, 
-                                       sslTest._inputStreamHolder);
+                                       true);
         
         Test testSetup =
             SecurityManagerSetup.noSecurityManager(networkServerTestSetup);

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java?rev=634425&r1=634424&r2=634425&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/SecureServerTest.java
Thu Mar  6 13:30:48 2008
@@ -41,6 +41,7 @@
 import org.apache.derbyTesting.junit.NetworkServerTestSetup;
 import org.apache.derbyTesting.junit.SecurityManagerSetup;
 import org.apache.derbyTesting.junit.ServerSetup;
+import org.apache.derbyTesting.junit.SpawnedProcess;
 import org.apache.derbyTesting.junit.SupportFilesSetup;
 import org.apache.derbyTesting.junit.SystemPropertyTestSetup;
 import org.apache.derbyTesting.junit.TestConfiguration;
@@ -121,9 +122,6 @@
     // expected outcomes
     private Outcome _outcome;
 
-    // helper state for intercepting server error messages
-    private InputStream[]  _inputStreamHolder;
-
     
     ///////////////////////////////////////////////////////////////////////////////////
     //
@@ -149,8 +147,6 @@
          _wildCardHost = wildCardHost;
 
          _outcome = outcome;
-
-         _inputStreamHolder = new InputStream[ 1 ];
     }
 
     ///////////////////////////////////////////////////////////////////////////////////
@@ -197,14 +193,6 @@
         
         return suite;
     }
-    
-    /**
-     * Release resources.
-     */
-    protected void tearDown() throws Exception
-    {
-        _inputStreamHolder = null;
-    }
 
 
     ///////////////////////////////////////////////////////////////////////////////////
@@ -248,8 +236,7 @@
              secureServerTest,
              startupProperties,
              startupArgs,
-             secureServerTest._outcome.serverShouldComeUp(),
-             secureServerTest._inputStreamHolder
+             secureServerTest._outcome.serverShouldComeUp()
              );
 
         secureServerTest.nsTestSetup = networkServerTestSetup;
@@ -474,26 +461,20 @@
                  }
              }
             );
-
-        InputStream is = serverProcess.getInputStream();
         
-        return getProcessOutput( is, 10000 );
+        SpawnedProcess spawned = new SpawnedProcess(serverProcess,
+                commandSpecifics);
+        
+        // Ensure it completes without failures.
+        assertEquals(0, spawned.complete(false));
+        
+        return spawned.getFullServerOutput();
     }
 
     private String  getServerOutput()
         throws Exception
     {
-        return getProcessOutput( _inputStreamHolder[ 0 ], 1000 );
-    }
-
-    private String  getProcessOutput( InputStream is, int bufferLength )
-        throws Exception
-    {
-        byte[]          inputBuffer = new byte[ bufferLength ];
-
-        int             bytesRead = is.read( inputBuffer );
-
-        return new String( inputBuffer, 0, bytesRead );
+        return nsTestSetup.getServerProcess().getNextServerOutput();
     }
 
     private static  String  serverBootedOK()
@@ -506,7 +487,7 @@
     {
         return NetworkServerTestSetup.pingForServerUp(
             NetworkServerTestSetup.getNetworkServerControl(),
-            nsTestSetup.getServerProcess(), true);
+            nsTestSetup.getServerProcess().getProcess(), true);
     }
 
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/ServerPropertiesTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/ServerPropertiesTest.java?rev=634425&r1=634424&r2=634425&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/ServerPropertiesTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/ServerPropertiesTest.java
Thu Mar  6 13:30:48 2008
@@ -60,10 +60,6 @@
 
 public class ServerPropertiesTest  extends BaseJDBCTestCase {
     
-    // helper state for intercepting server error messages;
-    // needed by fixture testToggleTrace
-    private InputStream[]  _inputStreamHolder;
-    
     //create own policy file
     private static String POLICY_FILE_NAME = 
         "functionTests/tests/derbynet/ServerPropertiesTest.policy";
@@ -72,7 +68,6 @@
     
     public ServerPropertiesTest(String name) {
         super(name);
-        _inputStreamHolder = new InputStream[1];
     }
     
     public static Test suite()
@@ -133,7 +128,6 @@
         super.tearDown();
         POLICY_FILE_NAME = null;
         TARGET_POLICY_FILE_NAME = null;
-        _inputStreamHolder = null;
         if (portsSoFar != null)
         {
             for (int i = 0 ; i < portsSoFar.length ; i++)
@@ -171,7 +165,7 @@
         {
             // start networkServer as a process
             networkServerTestSetup = new NetworkServerTestSetup(
-                spt, startupProps, startupArgs, true, spt._inputStreamHolder);
+                spt, startupProps, startupArgs, true);
         }
         else
         {

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/management/MBeanTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/management/MBeanTest.java?rev=634425&r1=634424&r2=634425&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/management/MBeanTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/management/MBeanTest.java
Thu Mar  6 13:30:48 2008
@@ -99,8 +99,7 @@
                         suite, // run all tests in this class in the same setup
                         getCommandLineProperties(), // need to set up JMX in JVM
                         new String[0], // no server arguments needed
-                        true,   // wait for the server to start properly
-                        new InputStream[1] // no need to check server output
+                        true   // wait for the server to start properly
                 );
 
         /* Since the server will be started in a new process we need "execute" 

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java?rev=634425&r1=634424&r2=634425&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
Thu Mar  6 13:30:48 2008
@@ -23,10 +23,13 @@
 import java.net.InetAddress;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import junit.framework.Test;
 import org.apache.derby.drda.NetworkServerControl;
@@ -47,10 +50,10 @@
      *  systems with fast port turnaround as the actual code loops for 
      *  SLEEP_TIME intervals, so should never see WAIT_TIME.
      */
-    private static final long WAIT_TIME = 300000;
+    private static final long WAIT_TIME = 10000;
     
     /** Sleep for 500 ms before pinging the network server (again) */
-    private static final int SLEEP_TIME = 500;
+    private static final int SLEEP_TIME = 100;
 
     public static final String HOST_OPTION = "-h";
 
@@ -62,7 +65,6 @@
     private final boolean startServerAtSetup;
     private final boolean useSeparateProcess;
     private final boolean serverShouldComeUp;
-    private final InputStream[] inputStreamHolder;
     
     /**
      * System properties to set on the command line (using -D)
@@ -74,8 +76,11 @@
      * Startup arguments for the command line
      * only when starting the server in a separate virtual machine.
      */
-    private final String[]    startupArgs;
-    private Process serverProcess;
+    private String[]    startupArgs;
+    /**
+     * The server as a process if started in a different vm.
+     */
+    private SpawnedProcess spawnedServer;
     
     /**
      * Decorator this test with the NetworkServerTestSetup.
@@ -97,7 +102,6 @@
         this.startupArgs = null;
         this.useSeparateProcess = false;
         this.serverShouldComeUp = true;
-        this.inputStreamHolder = null;
         this.startServerAtSetup = true;
 }
 
@@ -122,7 +126,6 @@
         this.startupArgs = null;
         this.useSeparateProcess = false;
         this.serverShouldComeUp = true;
-        this.inputStreamHolder = null;
 
         this.startServerAtSetup = startServerAtSetup;
     }
@@ -131,14 +134,24 @@
      * Decorator for starting up with specific command args
      * and system properties. Server is always started up
      * in a separate process with a separate virtual machine.
+     * <P>
+     * If the classes are being loaded from the classes
+     * folder instead of jar files then this will start
+     * the server up with no security manager using -noSecurityManager,
+     * unless the systemProperties or startupArgs set up any security
+     * manager.
+     * This is because the default policy
+     * installed by the network server only works from jar files.
+     * If this not desired then the test should skip the
+     * fixtures when loading from classes or
+     * install its own security manager.
      */
     public NetworkServerTestSetup
         (
          Test test,
          String[] systemProperties,
          String[] startupArgs,
-         boolean serverShouldComeUp,
-         InputStream[] inputStreamHolder
+         boolean serverShouldComeUp
         )
     {
         super(test);
@@ -149,7 +162,6 @@
         this.startupArgs = startupArgs;
         this.useSeparateProcess = true;
         this.serverShouldComeUp = serverShouldComeUp;
-        this.inputStreamHolder = inputStreamHolder;
         this.startServerAtSetup = true;
     }
 
@@ -164,13 +176,26 @@
         if (startServerAtSetup)
         {
             if (useSeparateProcess)
-            { serverProcess = startSeparateProcess(); }
+            { spawnedServer = startSeparateProcess(); }
             else if (asCommand)
             { startWithCommand(); }
             else
             { startWithAPI(); }
 
-            if ( serverShouldComeUp ) { waitForServerStart(networkServerController); }
+            if (serverShouldComeUp)
+            {
+                if (!pingForServerStart(networkServerController)) {
+                    String msg = "Timed out waiting for network server to start";
+                    // Dump the output from the spawned process
+                    // and destroy it.
+                    if (spawnedServer != null) {
+                        spawnedServer.complete(true);
+                        msg = spawnedServer.getFailMessage(msg);
+                        spawnedServer = null;
+                    }
+                    fail(msg);
+                }
+            }
         }
     }
 
@@ -214,7 +239,7 @@
         }, "NetworkServerTestSetup command").start();
     }
 
-    private Process startSeparateProcess() throws Exception
+    private SpawnedProcess startSeparateProcess() throws Exception
     {
         ArrayList       al = new ArrayList();
         String              classpath = BaseTestCase.getSystemProperty( "java.class.path"
);
@@ -223,6 +248,36 @@
         al.add( "java" );
         al.add( "-classpath" );
         al.add( classpath );
+        
+        // Loading from classes need to work-around the limitation
+        // of the default policy file doesn't work with classes.
+        if (!TestConfiguration.loadingFromJars())
+        {
+            boolean setNoSecurityManager = true;
+            for (int i = 0; i < systemProperties.length; i++)
+            {
+                if (systemProperties[i].startsWith("java.security."))
+                {
+                    setNoSecurityManager = false;
+                    break;
+                }
+            }
+            for (int i = 0; i < startupArgs.length; i++)
+            {
+                if (startupArgs[i].equals("-noSecurityManager"))
+                {
+                    setNoSecurityManager = false;
+                    break;
+                }
+            }
+            if (setNoSecurityManager)
+            {
+                String[] newArgs = new String[startupArgs.length + 1];
+                System.arraycopy(startupArgs, 0, newArgs, 0, startupArgs.length);
+                newArgs[newArgs.length - 1] = "-noSecurityManager";
+                startupArgs = newArgs;
+            }
+        }
 
         int         count = systemProperties.length;
         for ( int i = 0; i < count; i++ )
@@ -264,34 +319,33 @@
         System.out.println();
         */
 
-        Process     serverProcess = (Process) AccessController.doPrivileged
-            (
-             new PrivilegedAction()
-             {
-                 public Object run()
+        Process serverProcess;
+        
+        try {
+            serverProcess = (Process)
+                AccessController.doPrivileged
+                (
+                 new PrivilegedExceptionAction()
                  {
-                     Process    result = null;
-                     try {
-                        result = Runtime.getRuntime().exec( command );
-                     } catch (Exception ex) {
-                         ex.printStackTrace();
+                     public Object run() throws IOException
+                     {
+                         return Runtime.getRuntime().exec(command);
                      }
-                     
-                     return result;
                  }
-             }
-            );
+                );
+        } catch (PrivilegedActionException e) {
+            throw e.getException();
+        }
 
-        inputStreamHolder[ 0 ] = serverProcess.getInputStream();
-        return serverProcess;
+        return new SpawnedProcess(serverProcess, "SpawnedNetworkServer");
     }
 
     /**
      * Returns the <code>Process</code> object for the server process or <code>null</code>
if the
      * network server does not run in a separate process
      */
-    public Process getServerProcess() {
-        return serverProcess;
+    public SpawnedProcess getServerProcess() {
+        return spawnedServer;
     }
 
     /**
@@ -322,9 +376,9 @@
             networkServerController = null;
             serverOutput = null;
 
-            if (serverProcess != null) {
-                serverProcess.waitFor();
-                serverProcess = null;
+            if (spawnedServer != null) {
+                spawnedServer.complete(false);
+                spawnedServer = null;
             }
         }
     }
@@ -436,8 +490,9 @@
     public static void waitForServerStart(NetworkServerControl networkServerController)
        throws InterruptedException 
     {
-        if (!pingForServerStart(networkServerController))
-            fail("Timed out waiting for network server to start");
+        if (!pingForServerStart(networkServerController)) {
+             fail("Timed out waiting for network server to start");
+        }
     }
     
      /**

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java?rev=634425&view=auto
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java Thu
Mar  6 13:30:48 2008
@@ -0,0 +1,198 @@
+/*
+ *
+ * Derby - Class org.apache.derbyTesting.junit.SpawnedProcess
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, 
+ * software distributed under the License is distributed on an 
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
+ * either express or implied. See the License for the specific 
+ * language governing permissions and limitations under the License.
+ */
+package org.apache.derbyTesting.junit;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+/**
+ * Utility code that wraps a spawned process (Java Process object).
+ * Handles the output streams (stderr and stdout) written
+ * by the process by spawning off background threads to read
+ * them into byte arrays. The class provides access to the
+ * output, typically called once the process is complete.
+ */
+public final class SpawnedProcess {
+
+    private final String name;
+
+    private final Process javaProcess;
+
+    private final ByteArrayOutputStream err;
+
+    private final ByteArrayOutputStream out;
+
+    public SpawnedProcess(Process javaProcess, String name) {
+        this.javaProcess = javaProcess;
+        this.name = name;
+
+        err = streamSaver(javaProcess.getErrorStream(), name
+                .concat(":System.err"));
+        out = streamSaver(javaProcess.getInputStream(), name
+                .concat(":System.out"));
+    }
+
+    /**
+     * Get the Java Process object
+     */
+    public Process getProcess() {
+        return javaProcess;
+    }
+    
+    /**
+     * Get the full server output (stdout) as a string using the default
+     * encoding which is assumed is how it was orginally
+     * written.
+     */
+    public String getFullServerOutput() throws Exception {
+        Thread.sleep(500);
+        synchronized (this) {
+            return out.toString(); 
+        }
+    }
+    
+    /**
+     * Position offset for getNextServerOutput().
+     */
+    int stdOutReadOffset;
+    /**
+     * Get the next set of server output (stdout) as a string using the default
+     * encoding which is assumed is how it was orginally
+     * written. Assumes a single caller is executing the calls
+     * to this method.
+     */
+    public String getNextServerOutput() throws Exception
+    {
+        byte[] fullData;
+        synchronized (this) {
+            fullData = out.toByteArray();
+        }
+        
+        String output = new String(fullData, stdOutReadOffset,
+                fullData.length - stdOutReadOffset);
+        stdOutReadOffset = fullData.length;
+        return output;
+    }
+    /**
+     * Get a fail message that is the passed in reason plus
+     * the stderr and stdout for any output written. Allows
+     * easier debugging if the reason the process failed is there!
+     */
+    public String getFailMessage(String reason) throws InterruptedException
+    {
+        Thread.sleep(500);
+        StringBuffer sb = new StringBuffer();
+        sb.append(reason);
+        sb.append(":Spawned ");
+        sb.append(name);
+        sb.append(" exitCode=");
+        try {
+            sb.append(javaProcess.exitValue());
+        } catch (IllegalThreadStateException e) {
+            sb.append("running");
+        }
+        
+        synchronized (this) {
+            if (err.size() != 0)
+            {
+                sb.append("\nSTDERR:\n");
+                sb.append(err.toString());          
+            }
+            if (out.size() != 0)
+            {
+                sb.append("\nSTDOUT:\n");
+                sb.append(out.toString());          
+            }
+       }
+       return sb.toString();
+    }
+
+    /**
+     * Complete the method.
+     * @param destroy True to destroy it, false to wait for it to complete.
+     */
+    public int complete(boolean destroy) throws InterruptedException, IOException {
+        if (destroy)
+            javaProcess.destroy();
+
+        int exitCode = javaProcess.waitFor();
+        Thread.sleep(500);
+        synchronized (this) {
+
+            // Always write the error
+            if (err.size() != 0) {
+                System.err.println("START-SPAWNED:" + name + " ERROR OUTPUT:");
+                err.writeTo(System.err);
+                System.err.println("END-SPAWNED  :" + name + " ERROR OUTPUT:");
+            }
+
+            // Only write the error if it appeared the server
+            // failed in some way.
+            if ((destroy || exitCode != 0) && out.size() != 0) {
+                System.out.println("START-SPAWNED:" + name
+                        + " STANDARD OUTPUT: exit code=" + exitCode);
+                out.writeTo(System.out);
+                System.out.println("END-SPAWNED  :" + name
+                        + " STANDARD OUTPUT:");
+            }
+        }
+        
+        return exitCode;
+    }
+
+    private ByteArrayOutputStream streamSaver(final InputStream in,
+            final String name) {
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream() {
+            public void reset() {
+                super.reset();
+                new Throwable("WWW").printStackTrace(System.out);
+            }
+
+        };
+
+        Thread streamReader = new Thread(new Runnable() {
+
+            public void run() {
+                try {
+                    byte[] buffer = new byte[1024];
+                    int read;
+                    while ((read = in.read(buffer)) != -1) {
+                        synchronized (SpawnedProcess.this) {
+                            out.write(buffer, 0, read);
+                        }
+                    }
+
+                } catch (IOException ioe) {
+                    ioe.printStackTrace(new PrintStream(out, true));
+                }
+            }
+
+        }, name);
+        streamReader.setDaemon(true);
+        streamReader.setPriority(Thread.MIN_PRIORITY);
+        streamReader.start();
+
+        return out;
+
+    }
+}

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SpawnedProcess.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message