View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.surefire.testng.conf;
20  
21  import java.lang.reflect.Method;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import org.apache.maven.surefire.api.testset.TestSetFailedException;
27  import org.testng.TestNG;
28  import org.testng.xml.XmlSuite;
29  
30  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.PARALLEL_PROP;
31  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.THREADCOUNT_PROP;
32  import static org.apache.maven.surefire.testng.conf.AbstractDirectConfigurator.loadListenerClasses;
33  
34  /**
35   * TestNG configurator for 5.3+ versions. TestNG exposes a {@link org.testng.TestNG#configure(java.util.Map)} method.
36   * All supported TestNG options are passed in String format, except
37   * {@link org.testng.TestNGCommandLineArgs#LISTENER_COMMAND_OPT} which is {@link java.util.List List>Class<},
38   * {@link org.testng.TestNGCommandLineArgs#JUNIT_DEF_OPT} which is a {@link Boolean},
39   * {@link org.testng.TestNGCommandLineArgs#SKIP_FAILED_INVOCATION_COUNT_OPT} which is a {@link Boolean},
40   * {@link org.testng.TestNGCommandLineArgs#OBJECT_FACTORY_COMMAND_OPT} which is a {@link Class},
41   * {@link org.testng.TestNGCommandLineArgs#REPORTERS_LIST} which is a {@link java.util.List List>ReporterConfig<}.
42   * <br>
43   * Test classes and/or suite files are not passed along as options parameters, but configured separately.
44   *
45   * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
46   */
47  public class TestNGMapConfigurator implements Configurator {
48      @Override
49      public void configure(TestNG testng, Map<String, String> options) throws TestSetFailedException {
50          Map convertedOptions = getConvertedOptions(options);
51          testng.configure(convertedOptions);
52      }
53  
54      @Override
55      public void configure(XmlSuite suite, Map<String, String> options) throws TestSetFailedException {
56          configureThreadCount(suite, options);
57          configureParallel(suite, options);
58      }
59  
60      protected void configureThreadCount(XmlSuite suite, Map<String, String> options) throws TestSetFailedException {
61          String threadCount = options.get(THREADCOUNT_PROP);
62          if (threadCount != null) {
63              try {
64                  suite.setThreadCount(Integer.parseInt(threadCount));
65              } catch (NumberFormatException e) {
66                  throw new TestSetFailedException("Non-integer TestNG [threadcount] setting: " + threadCount, e);
67              }
68          }
69      }
70  
71      protected void configureParallel(XmlSuite suite, Map<String, String> options) throws TestSetFailedException {
72          String parallel = options.get(PARALLEL_PROP);
73          if (parallel != null) {
74              suite.setParallel(parallel);
75          }
76      }
77  
78      Map<String, Object> getConvertedOptions(Map<String, String> options) throws TestSetFailedException {
79          Map<String, Object> convertedOptions = new HashMap<>();
80          convertedOptions.put("-mixed", false);
81          for (Map.Entry<String, String> entry : options.entrySet()) {
82              String key = entry.getKey();
83              Object val = entry.getValue();
84              switch (key) {
85                  case "listener":
86                      val = convertListeners(entry.getValue());
87                      break;
88                  case "objectfactory":
89                  case "testrunfactory":
90                      val = AbstractDirectConfigurator.loadClass(entry.getValue());
91                      break;
92                  case "reporter":
93                      // for TestNG 5.6 or higher
94                      // TODO support multiple reporters?
95                      val = convertReporterConfig(val);
96                      key = "reporterslist";
97                      break;
98                  case "junit":
99                  case "skipfailedinvocationcounts":
100                 case "mixed":
101                 case "group-by-instances":
102                     val = convert(val, Boolean.class);
103                     break;
104                 case "configfailurepolicy":
105                 case THREADCOUNT_PROP:
106                     val = convert(val, String.class);
107                     break;
108                     // for TestNG 6.9.7 or higher
109                 case "suitethreadpoolsize":
110                     // for TestNG 5.10 or higher
111                 case "dataproviderthreadcount":
112                     val = convert(val, Integer.class);
113                     break;
114                 default:
115                     break;
116             }
117 
118             if (key.startsWith("-")) {
119                 convertedOptions.put(key, val);
120             } else {
121                 convertedOptions.put("-" + key, val);
122             }
123         }
124         return convertedOptions;
125     }
126 
127     // ReporterConfig only became available in later versions of TestNG
128     protected Object convertReporterConfig(Object val) {
129         try {
130             Class<?> reporterConfig = Class.forName("org.testng.ReporterConfig");
131             Method deserialize = reporterConfig.getMethod("deserialize", String.class);
132             Object rc = deserialize.invoke(null, val);
133             ArrayList<Object> reportersList = new ArrayList<>();
134             reportersList.add(rc);
135             return reportersList;
136         } catch (Exception e) {
137             return val;
138         }
139     }
140 
141     protected Object convertListeners(String listenerClasses) throws TestSetFailedException {
142         return loadListenerClasses(listenerClasses);
143     }
144 
145     protected Object convert(Object val, Class<?> type) {
146         if (val == null) {
147             return null;
148         } else if (type.isAssignableFrom(val.getClass())) {
149             return val;
150         } else if ((type == Boolean.class || type == boolean.class) && val.getClass() == String.class) {
151             return Boolean.valueOf((String) val);
152         } else if ((type == Integer.class || type == int.class) && val.getClass() == String.class) {
153             return Integer.valueOf((String) val);
154         } else if (type == String.class) {
155             return val.toString();
156         } else {
157             return val;
158         }
159     }
160 }