jmeter-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fschumac...@apache.org
Subject svn commit: r1816319 - in /jmeter/trunk: LICENSE build.properties build.xml eclipse.classpath res/maven/ApacheJMeter_parent.pom test/src/org/apache/jmeter/JMeterVersionTest.java test/src/org/apache/jorphan/test/AllTests.java xdocs/changes.xml
Date Sat, 25 Nov 2017 16:28:12 GMT
Author: fschumacher
Date: Sat Nov 25 16:28:12 2017
New Revision: 1816319

URL: http://svn.apache.org/viewvc?rev=1816319&view=rev
Log:
Add spock framework for groovy unit tests.

This is part of pr #332 from github.
Contributed by Graham Russell

Modified:
    jmeter/trunk/LICENSE
    jmeter/trunk/build.properties
    jmeter/trunk/build.xml
    jmeter/trunk/eclipse.classpath
    jmeter/trunk/res/maven/ApacheJMeter_parent.pom
    jmeter/trunk/test/src/org/apache/jmeter/JMeterVersionTest.java
    jmeter/trunk/test/src/org/apache/jorphan/test/AllTests.java
    jmeter/trunk/xdocs/changes.xml

Modified: jmeter/trunk/LICENSE
URL: http://svn.apache.org/viewvc/jmeter/trunk/LICENSE?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/LICENSE [utf-8] (original)
+++ jmeter/trunk/LICENSE [utf-8] Sat Nov 25 16:28:12 2017
@@ -211,6 +211,7 @@ The following software is provided under
 
 * apache-bsf-2.4.0.jar
 * beanshell-2.0b6.jar
+* cglib-nodep-3.2.5.jar
 * commons-codec-1.10.jar
 * commons-collections-3.2.2.jar
 * commons-dbcp2-2.1.1.jar
@@ -252,8 +253,10 @@ The following software is provided under
 * json-path-2.4.0.jar
 * json-smart-2.3.jar
 * mongo-java-driver-2.11.3.jar
+* objenesis-2.6.jar
 * ph-commons-8.6.6.jar
 * ph-css-5.0.4.jar
+* spock-1.0-groovy-2.4.jar
 
 - Software produced outside the ASF which is available under other licenses (not AL 2.0):
 For details, please see the files under: licenses/bin

Modified: jmeter/trunk/build.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/build.properties?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/build.properties (original)
+++ jmeter/trunk/build.properties Sat Nov 25 16:28:12 2017
@@ -263,6 +263,21 @@ junit.jar                   = junit-${ju
 junit.loc                   = ${maven2.repo}/junit/junit/${junit.version}
 junit.md5                   = 5b38c40c97fbd0adee29f91e60405584
 
+spock.version               = 1.0-groovy-2.4
+spock.jar                   = spock-core-${spock.version}.jar
+spock.loc                   = ${maven2.repo}/org/spockframework/spock-core/${spock.version}
+spock.md5                   = 2fbbeaf95dd10b445c6c581c9b945075
+
+cglib-nodep.version         = 3.2.5
+cglib-nodep.jar             = cglib-nodep-${cglib-nodep.version}.jar
+cglib-nodep.loc             = ${maven2.repo}/cglib/cglib-nodep/${cglib-nodep.version}
+cglib-nodep.md5             = 584a4c2842da823027ff2d2278b7d830
+
+objenesis.version           = 2.6
+objenesis.jar               = objenesis-${objenesis.version}.jar
+objenesis.loc               = ${maven2.repo}/org/objenesis/objenesis/${objenesis.version}
+objenesis.md5               = 5ffac3f51405ca9b2915970a224b3e8f
+
 mongo-java-driver.version   = 2.11.3
 mongo-java-driver.jar       = mongo-java-driver-${mongo-java-driver.version}.jar
 mongo-java-driver.loc       = ${maven2.repo}/org/mongodb/mongo-java-driver/${mongo-java-driver.version}

Modified: jmeter/trunk/build.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/build.xml?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/build.xml (original)
+++ jmeter/trunk/build.xml Sat Nov 25 16:28:12 2017
@@ -519,6 +519,9 @@
     <pathelement location="${lib.dir}/${serializer.jar}"/>
     <pathelement location="${lib.dir}/${slf4j-api.jar}"/>
     <pathelement location="${lib.dir}/${slf4j-ext.jar}"/>
+    <pathelement location="${lib.dir}/${spock.jar}"/>
+    <pathelement location="${lib.dir}/${cglib-nodep.jar}"/>
+    <pathelement location="${lib.dir}/${objenesis.jar}"/>
     <pathelement location="${lib.dir}/${jtidy.jar}"/>
     <pathelement location="${lib.dir}/${tika-core.jar}"/>
     <pathelement location="${lib.dir}/${tika-parsers.jar}"/>
@@ -726,6 +729,35 @@
         <path refid="logging.classpath"/>
       </classpath>
     </javac>
+
+    <!-- Compile Spock tests (groovy) -->
+    <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc">
+      <classpath>
+        <fileset dir="${lib.dir}" includes="*.jar"/>
+      </classpath>
+    </taskdef>
+    <groovyc srcdir="${src.test}" destdir="${build.test}" encoding="${encoding}">
+      <classpath>
+        <pathelement location="${build.jorphan}"/>
+        <pathelement location="${build.core}"/>
+        <pathelement location="${build.components}"/>
+        <pathelement location="${build.http}"/>
+        <pathelement location="${build.ftp}"/>
+        <pathelement location="${build.functions}"/>
+        <pathelement location="${build.java}"/>
+        <pathelement location="${build.jdbc}"/>
+        <pathelement location="${build.ldap}"/>
+        <pathelement location="${build.mail}"/>
+        <pathelement location="${build.tcp}"/>
+        <!-- Include compiled jars to allow running tests without rebuilding source -->
+        <fileset dir="${dest.jar}" includes="*.jar"/>
+        <path refid="classpath"/>
+      </classpath>
+      <!-- Compile JUnit tests (Java) -->
+      <javac source="${src.java.version}" debug="on" target="${target.java.version}"
+             deprecation="${deprecation}" encoding="${encoding}">
+      </javac>
+    </groovyc>
   </target>
 
   <target name="compile-ftp" depends="compile-jorphan,compile-core" description="Compile
components specific to FTP sampling.">
@@ -3219,6 +3251,9 @@ run JMeter unless all the JMeter jars ar
     <process_jarfile jarname="log4j-1.2-api"/>
     <process_jarfile jarname="log4j-core"/>
     <process_jarfile jarname="log4j-slf4j-impl"/>
+    <process_jarfile jarname="spock"/>
+    <process_jarfile jarname="cglib-nodep"/>
+    <process_jarfile jarname="objenesis"/>
     <process_jarfile jarname="jtidy"/>
     <process_jarfile jarname="tika-core"/>
     <process_jarfile jarname="tika-parsers"/>

Modified: jmeter/trunk/eclipse.classpath
URL: http://svn.apache.org/viewvc/jmeter/trunk/eclipse.classpath?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/eclipse.classpath (original)
+++ jmeter/trunk/eclipse.classpath Sat Nov 25 16:28:12 2017
@@ -108,6 +108,9 @@
 	<classpathentry kind="lib" path="lib/xpp3_min-1.1.4c.jar"/>
 	<classpathentry kind="lib" path="lib/xstream-1.4.10.jar"/>
 	<!-- Needed for build and test -->
+	<classpathentry kind="lib" path="lib/spock-1.0-groovy-2.4.jar"/>
+	<classpathentry kind="lib" path="lib/cglib-nodep-3.2.5.jar"/>
+	<classpathentry kind="lib" path="lib/objenesis-2.6.jar"/>
 	<classpathentry kind="lib" path="lib/api/bcmail-jdk15on-1.49.jar"/>
 	<classpathentry kind="lib" path="lib/api/bcpkix-jdk15on-1.49.jar"/>
 	<classpathentry kind="lib" path="lib/api/bcprov-jdk15on-1.49.jar"/>

Modified: jmeter/trunk/res/maven/ApacheJMeter_parent.pom
URL: http://svn.apache.org/viewvc/jmeter/trunk/res/maven/ApacheJMeter_parent.pom?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/res/maven/ApacheJMeter_parent.pom (original)
+++ jmeter/trunk/res/maven/ApacheJMeter_parent.pom Sat Nov 25 16:28:12 2017
@@ -103,6 +103,9 @@ under the License.
       <log4j-core.version>2.8.2</log4j-core.version>
       <log4j-slf4j-impl.version>2.8.2</log4j-slf4j-impl.version>
       <log4j-1.2-api.version>2.8.2</log4j-1.2-api.version>
+      <spock.version>1.0-groovy-2.4</spock.version>
+      <cglib-nodep.version>3.2.5</cglib-nodep.version>
+      <objenesis.version>2.6</objenesis.version>
       <jtidy.version>r938</jtidy.version>
       <tika-core.version>1.16</tika-core.version>
       <tika-parsers.version>1.16</tika-parsers.version>
@@ -270,6 +273,21 @@ under the License.
         <version>${junit.version}</version>
       </dependency>
       <dependency>
+        <groupId>org.spockframework</groupId>
+        <artifactId>spock-core</artifactId>
+        <version>${spock.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>cglib</groupId>
+        <artifactId>cglib-nodep</artifactId>
+        <version>${cglib-nodep.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.objenesis</groupId>
+        <artifactId>objenesis</artifactId>
+        <version>${objenesis.version}</version>
+      </dependency>
+      <dependency>
         <groupId>net.sf.jtidy</groupId>
         <artifactId>jtidy</artifactId>
         <version>${jtidy.version}</version>

Modified: jmeter/trunk/test/src/org/apache/jmeter/JMeterVersionTest.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/JMeterVersionTest.java?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/test/src/org/apache/jmeter/JMeterVersionTest.java (original)
+++ jmeter/trunk/test/src/org/apache/jmeter/JMeterVersionTest.java Sat Nov 25 16:28:12 2017
@@ -26,7 +26,7 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileReader;
-import java.io.FilenameFilter;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -37,6 +37,7 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import org.apache.jmeter.junit.JMeterTestCase;
 import org.apache.jmeter.util.JMeterUtils;
@@ -72,10 +73,8 @@ public class JMeterVersionTest extends J
      */
     private final Set<String> propNames = new HashSet<>();
 
-    /**
-     * License file names found under license/bin (WITHOUT the .txt suffix)
-     */
-    private final Set<String> liceFiles = new HashSet<>();
+    /** License file names found under license/bin (WITHOUT the .txt suffix) */
+    private Set<String> liceFiles;
 
     private File getFileFromHome(String relativeFile) {
         return new File(JMETER_HOME, relativeFile);
@@ -84,7 +83,7 @@ public class JMeterVersionTest extends J
     private Properties prop;
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() throws IOException {
         final Properties buildProp = new Properties();
         final FileInputStream bp = new FileInputStream(getFileFromHome("build.properties"));
         buildProp.load(bp);
@@ -128,24 +127,19 @@ public class JMeterVersionTest extends J
         }
         prop = buildProp;
         final File licencesDir = getFileFromHome("licenses/bin");
-        licencesDir.list(new FilenameFilter() {
-            @Override
-            public boolean accept(File dir, String name) {
-                if (! name.equalsIgnoreCase("README.txt") 
-                        && !name.equals(".svn")) { // Allow for old-style SVN workspaces
-                    liceFiles.add(name.replace(".txt",""));
-                }
-                return false;
-            }
-        });
+        liceFiles = Arrays.stream(licencesDir.list())
+                .filter(name -> !name.equalsIgnoreCase("README.txt"))
+                .filter(name -> !name.equals(".svn")) // Ignore old-style SVN workspaces
+                .map(name -> name.replace(".txt", ""))
+                .collect(Collectors.toSet());
     }
 
     /**
      * Check eclipse.classpath contains the jars declared in build.properties
-     * @throws Exception if something fails
+     * @throws IOException if something fails
      */
     @Test
-    public void testEclipse() throws Exception {
+    public void testEclipse() throws IOException {
         final BufferedReader eclipse = new BufferedReader(
                 new FileReader(getFileFromHome("eclipse.classpath"))); // assume default
charset is OK here
 //      <classpathentry kind="lib" path="lib/geronimo-jms_1.1_spec-1.1.1.jar"/>
@@ -163,7 +157,10 @@ public class JMeterVersionTest extends J
                 if (jar.endsWith("-jdk15on")) { // special handling
                     jar=jar.replace("-jdk15on","");
                 } else if (jar.equals("commons-jexl") && version.startsWith("2"))
{ // special handling
-                    jar="commons-jexl2";
+                    jar = "commons-jexl2";
+                } else if (jar.equals("spock-1.0-groovy")) { // special handling
+                    jar = "spock";
+                    version = "1.0-groovy-2.4";
                 } else {
                     String tmp = JAR_TO_BUILD_PROP.get(jar);
                     if (tmp != null) {
@@ -193,17 +190,17 @@ public class JMeterVersionTest extends J
             }
         }
         // remove any possibly unused references
-        for(Object key : toRemove.toArray()) {
-            propNames.remove(key);            
-        }
+        propNames.removeAll(toRemove);
         eclipse.close();
         if (propNames.size() > 0) {
-            fail("Should have no names left: "+Arrays.toString(propNames.toArray()) + ".
Check eclipse.classpath");
+            fail("Should have no names left: "
+                    + Arrays.toString(propNames.toArray())
+                    + ". Check eclipse.classpath");
         }
     }
 
     @Test
-    public void testMaven() throws Exception {
+    public void testMaven() throws IOException {
         final BufferedReader maven = new BufferedReader(
                 new FileReader(getFileFromHome("res/maven/ApacheJMeter_parent.pom"))); //
assume default charset is OK here
 //      <apache-bsf.version>2.4.0</apache-bsf.version>
@@ -229,16 +226,18 @@ public class JMeterVersionTest extends J
         }
         maven.close();
         if (propNames.size() > 0) {
-            fail("Should have no names left: "+Arrays.toString(propNames.toArray()) + ".
Check ApacheJMeter_parent.pom");
+            fail("Should have no names left: "
+                    + Arrays.toString(propNames.toArray())
+                    + ". Check ApacheJMeter_parent.pom");
         }
-   }
+    }
 
     @Test
     public void testLicences() {
         Set<String> liceNames = new HashSet<>();
         for (Map.Entry<String, String> me : versions.entrySet()) {
-        final String key = me.getKey();
-            liceNames.add(key+"-"+me.getValue());
+            final String key = me.getKey();
+            liceNames.add(key + "-" + me.getValue());
         }
         assertTrue("Expected at least one license file", liceFiles.size() > 0);
         for(String l : liceFiles) {
@@ -258,7 +257,7 @@ public class JMeterVersionTest extends J
             final String key = me.getKey();
             final String jarName = key + "-" + me.getValue();
             if (propNames.contains(key) && !buildOnly.contains(key)) {
-              binaryJarNames.add(jarName);                
+                binaryJarNames.add(jarName);
             }
         }
         // Extract the jar names from LICENSE
@@ -275,8 +274,8 @@ public class JMeterVersionTest extends J
             if (m.matches()) {
                 final String name = m.group(1);
                 assertTrue("Duplicate jar in LICENSE file " + line, namesInLicenseFile.add(name));
-                if (!binaryJarNames.contains(name) && !(line.indexOf("darcula")>=0))
{
-                    fail("Unexpected entry in LICENCE file: " + line);                  
 
+                if (!binaryJarNames.contains(name) && !line.contains("darcula"))
{
+                    fail("Unexpected entry in LICENCE file: " + line);
                 }
                 final String comment = m.group(2);
                 if (comment.length() > 0) { // must be in external list

Modified: jmeter/trunk/test/src/org/apache/jorphan/test/AllTests.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jorphan/test/AllTests.java?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/test/src/org/apache/jorphan/test/AllTests.java (original)
+++ jmeter/trunk/test/src/org/apache/jorphan/test/AllTests.java Sat Nov 25 16:28:12 2017
@@ -22,10 +22,10 @@ import java.awt.GraphicsEnvironment;
 import java.io.File;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.nio.charset.Charset;
 import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 
@@ -43,15 +43,15 @@ import org.junit.runner.Computer;
 import org.junit.runner.JUnitCore;
 import org.junit.runner.Request;
 import org.junit.runner.Result;
-import org.junit.runner.notification.RunListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import spock.lang.Specification;
 
 import junit.framework.TestCase;
 
 /**
  * Provides a quick and easy way to run all <a href="http://http://junit.org">junit</a>

- * unit tests in your java project. 
+ * unit tests (including Spock tests) in your Java project.
  * It will find all unit test classes and run all their test methods.
  * There is no need to configure it in any way to find these classes except to
  * give it a path to search.
@@ -188,9 +188,8 @@ public final class AllTests {
         try {
             int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
             System.out.println("JCE max key length = " + maxKeyLen);
-        } catch (NoSuchAlgorithmException e1) {
-            // TODO Auto-generated catch block
-            e1.printStackTrace();
+        } catch (NoSuchAlgorithmException e) {
+            log.warn(e.getLocalizedMessage());
         }
         System.out.println("+++++++++++");
         logprop("java.awt.headless", true);
@@ -205,8 +204,7 @@ public final class AllTests {
             
             // this listener is in the internal junit package
             // if it breaks, replace it with a custom text listener
-            RunListener listener = new TextListener(new RealSystem());
-            jUnitCore.addListener(listener);
+            jUnitCore.addListener(new TextListener(new RealSystem()));
             
             Request request = Request.classes(new Computer(), classes);
             if(GraphicsEnvironment.isHeadless()) {
@@ -227,7 +225,7 @@ public final class AllTests {
             String test = tests.get(i);
             classes[i] = Class.forName(test, true, Thread.currentThread().getContextClassLoader());
         }
-        
+
         return classes;
     }
 
@@ -256,70 +254,59 @@ public final class AllTests {
         }
     }
 
-    private static List<String> findJMeterJUnitTests(String searchPaths)  throws IOException
{
-        List<String> classList = ClassFinder.findClasses(JOrphanUtils.split(searchPaths,
","), new JunitTestFilter());
-       
-        return classList;
+    private static List<String> findJMeterJUnitTests(String searchPathString) throws
IOException {
+        final String[] searchPaths = JOrphanUtils.split(searchPathString, ",");
+        return ClassFinder.findClasses(searchPaths, new JunitTestFilter());
     }
-    
+
     /**
-     * find the junit tests in the test search path
+     * Match JUnit (including Spock) tests
      */
     private static class JunitTestFilter implements ClassFilter {
-        
-        private final transient ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+
+        private final transient ClassLoader contextClassLoader =
+                Thread.currentThread().getContextClassLoader();
 
         @Override
         public boolean accept(String className) {
-            
             boolean isJunitTest = false;
             try {
-                Class<?> c = Class.forName(className, false, contextClassLoader);
+                Class<?> clazz = Class.forName(className, false, contextClassLoader);
 
-                if (!c.isAnnotation() 
-                        && !c.isEnum() 
-                        && !c.isInterface() 
-                        && !Modifier.isAbstract(c.getModifiers())) 
-                {
-                    if (TestCase.class.isAssignableFrom(c)) {
-                        isJunitTest =  true;
-                    }
-                    else {
-                        isJunitTest = checkForJUnitAnnotations(c);
-                    }
+                if (!clazz.isAnnotation()
+                        && !clazz.isEnum()
+                        && !clazz.isInterface()
+                        && !Modifier.isAbstract(clazz.getModifiers())) {
+                    isJunitTest = TestCase.class.isAssignableFrom(clazz)
+                            || Specification.class.isAssignableFrom(clazz) // Spock
+                            || checkForJUnitAnnotations(clazz);
                 }
-            } catch (UnsupportedClassVersionError | ClassNotFoundException
+            } catch (UnsupportedClassVersionError
+                    | ClassNotFoundException
                     | NoClassDefFoundError e) {
                 log.debug("Exception while filtering class {}. {}", className, e.toString());
             }
 
             return isJunitTest;
         }
-        
-        private boolean checkForJUnitAnnotations(Class<?> clazz)
-        {
+
+        private boolean checkForJUnitAnnotations(Class<?> clazz) {
             Class<?> classToCheck = clazz;
-            while(classToCheck != null) {
-                if( checkforTestAnnotationOnMethods(classToCheck)) {
+            while (classToCheck != null) {
+                if (checkForTestAnnotationOnMethods(classToCheck)) {
                     return true;
                 }
                 classToCheck = classToCheck.getSuperclass();
             }
-            
+
             return false;
         }
 
-        private boolean checkforTestAnnotationOnMethods(Class<?> clazz)
-        {
-            for(Method method : clazz.getDeclaredMethods()) {
-                for(Annotation annotation : method.getAnnotations() ) {
-                    if (org.junit.Test.class.isAssignableFrom(annotation.annotationType()))
{
-                        return true;
-                    }
-                }
-            }
-            
-            return false;
+        private boolean checkForTestAnnotationOnMethods(Class<?> clazz) {
+            return Arrays.stream(clazz.getDeclaredMethods())
+                    .flatMap(method -> Arrays.stream(method.getAnnotations()))
+                    .map(Annotation::annotationType)
+                    .anyMatch(org.junit.Test.class::isAssignableFrom);
         }
 
         /**
@@ -329,6 +316,5 @@ public final class AllTests {
         public String toString() {
             return "JunitTestFilter []";
         }
-        
     }
 }

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1816319&r1=1816318&r2=1816319&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Sat Nov 25 16:28:12 2017
@@ -188,6 +188,7 @@ Summary
     <li><pr>323</pr>Extracted method and used streams to improve readability.
Contributed by Graham Russell (graham at ham1.co.uk)</li>
     <li><pr>324</pr> Save backup refactor. Contributed by Graham Russell
(graham at ham1.co.uk)</li>
     <li><pr>327</pr> Utilising more modern Java, simplifying code and formatting
code and comments. Contributed by Graham Russell (graham at ham1.co.uk)</li>
+    <li><pr>332</pr>Add the spock framework for groovy unit tests. Contributed
by Graham Russell (graham at ham1.co.uk)</li>
     <li><pr>334</pr> Enable running of JUnit tests from within IntelliJ
with default config. Contributed by Graham Russell (graham at ham1.co.uk)</li>
     <li><pr>335</pr> Removed <code>functions.util.*</code>
as they don't seem to be used (for many years). Contributed by Graham Russell (graham at ham1.co.uk)</li>
 </ul>



Mime
View raw message