You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JvmOptionsTest.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.application.command;
  21. import com.google.common.collect.ImmutableMap;
  22. import com.tngtech.java.junit.dataprovider.DataProvider;
  23. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  24. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  25. import java.util.Collections;
  26. import java.util.HashMap;
  27. import java.util.List;
  28. import java.util.Locale;
  29. import java.util.Map;
  30. import java.util.Properties;
  31. import java.util.Random;
  32. import java.util.stream.Collectors;
  33. import java.util.stream.IntStream;
  34. import java.util.stream.Stream;
  35. import org.junit.Rule;
  36. import org.junit.Test;
  37. import org.junit.rules.ExpectedException;
  38. import org.junit.runner.RunWith;
  39. import org.sonar.process.MessageException;
  40. import org.sonar.process.Props;
  41. import static java.lang.String.valueOf;
  42. import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
  43. import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
  44. import static org.assertj.core.api.Assertions.assertThat;
  45. import static org.assertj.core.api.Assertions.fail;
  46. @RunWith(DataProviderRunner.class)
  47. public class JvmOptionsTest {
  48. @Rule
  49. public ExpectedException expectedException = ExpectedException.none();
  50. private final Random random = new Random();
  51. private final String randomPropertyName = randomAlphanumeric(3);
  52. private final String randomPrefix = "-" + randomAlphabetic(5).toLowerCase(Locale.ENGLISH);
  53. private final String randomValue = randomAlphanumeric(4).toLowerCase(Locale.ENGLISH);
  54. private final Properties properties = new Properties();
  55. private final JvmOptions underTest = new JvmOptions();
  56. @Test
  57. public void constructor_without_arguments_creates_empty_JvmOptions() {
  58. JvmOptions<JvmOptions> testJvmOptions = new JvmOptions<>();
  59. assertThat(testJvmOptions.getAll()).isEmpty();
  60. }
  61. @Test
  62. public void constructor_throws_NPE_if_argument_is_null() {
  63. expectJvmOptionNotNullNPE();
  64. new JvmOptions(null);
  65. }
  66. @Test
  67. public void constructor_throws_NPE_if_any_option_prefix_is_null() {
  68. Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
  69. Stream.of(
  70. IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
  71. Stream.of(new Option(null, "value")))
  72. .flatMap(s -> s));
  73. expectedException.expect(NullPointerException.class);
  74. expectedException.expectMessage("JVM option prefix can't be null");
  75. new JvmOptions(mandatoryJvmOptions);
  76. }
  77. @Test
  78. @UseDataProvider("variousEmptyStrings")
  79. public void constructor_throws_IAE_if_any_option_prefix_is_empty(String emptyString) {
  80. Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
  81. Stream.of(
  82. IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
  83. Stream.of(new Option(emptyString, "value")))
  84. .flatMap(s -> s));
  85. expectedException.expect(IllegalArgumentException.class);
  86. expectedException.expectMessage("JVM option prefix can't be empty");
  87. new JvmOptions(mandatoryJvmOptions);
  88. }
  89. @Test
  90. public void constructor_throws_IAE_if_any_option_prefix_does_not_start_with_dash() {
  91. String invalidPrefix = randomAlphanumeric(3);
  92. Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
  93. Stream.of(
  94. IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
  95. Stream.of(new Option(invalidPrefix, "value")))
  96. .flatMap(s -> s));
  97. expectJvmOptionNotEmptyAndStartByDashIAE();
  98. new JvmOptions(mandatoryJvmOptions);
  99. }
  100. @Test
  101. public void constructor_throws_NPE_if_any_option_value_is_null() {
  102. Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
  103. Stream.of(
  104. IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
  105. Stream.of(new Option("-prefix", null)))
  106. .flatMap(s -> s));
  107. expectedException.expect(NullPointerException.class);
  108. expectedException.expectMessage("JVM option value can't be null");
  109. new JvmOptions(mandatoryJvmOptions);
  110. }
  111. @Test
  112. @UseDataProvider("variousEmptyStrings")
  113. public void constructor_accepts_any_empty_option_value(String emptyString) {
  114. Map<String, String> mandatoryJvmOptions = shuffleThenToMap(
  115. Stream.of(
  116. IntStream.range(0, random.nextInt(10)).mapToObj(i -> new Option("-B", valueOf(i))),
  117. Stream.of(new Option("-prefix", emptyString)))
  118. .flatMap(s -> s));
  119. new JvmOptions(mandatoryJvmOptions);
  120. }
  121. @Test
  122. public void add_throws_NPE_if_argument_is_null() {
  123. expectJvmOptionNotNullNPE();
  124. underTest.add(null);
  125. }
  126. @Test
  127. @UseDataProvider("variousEmptyStrings")
  128. public void add_throws_IAE_if_argument_is_empty(String emptyString) {
  129. expectJvmOptionNotEmptyAndStartByDashIAE();
  130. underTest.add(emptyString);
  131. }
  132. @Test
  133. public void add_throws_IAE_if_argument_does_not_start_with_dash() {
  134. expectJvmOptionNotEmptyAndStartByDashIAE();
  135. underTest.add(randomAlphanumeric(3));
  136. }
  137. @Test
  138. @UseDataProvider("variousEmptyStrings")
  139. public void add_adds_with_trimming(String emptyString) {
  140. underTest.add(emptyString + "-foo" + emptyString);
  141. assertThat(underTest.getAll()).containsOnly("-foo");
  142. }
  143. @Test
  144. public void add_throws_MessageException_if_option_starts_with_prefix_of_mandatory_option_but_has_different_value() {
  145. String[] optionOverrides = {
  146. randomPrefix,
  147. randomPrefix + randomAlphanumeric(1),
  148. randomPrefix + randomAlphanumeric(2),
  149. randomPrefix + randomAlphanumeric(3),
  150. randomPrefix + randomAlphanumeric(4),
  151. randomPrefix + randomValue.substring(1),
  152. randomPrefix + randomValue.substring(2),
  153. randomPrefix + randomValue.substring(3)
  154. };
  155. JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
  156. for (String optionOverride : optionOverrides) {
  157. try {
  158. underTest.add(optionOverride);
  159. fail("an MessageException should have been thrown");
  160. } catch (MessageException e) {
  161. assertThat(e.getMessage()).isEqualTo("a JVM option can't overwrite mandatory JVM options. " + optionOverride + " overwrites " + randomPrefix + randomValue);
  162. }
  163. }
  164. }
  165. @Test
  166. public void add_checks_against_mandatory_options_is_case_sensitive() {
  167. String[] optionOverrides = {
  168. randomPrefix,
  169. randomPrefix + randomAlphanumeric(1),
  170. randomPrefix + randomAlphanumeric(2),
  171. randomPrefix + randomAlphanumeric(3),
  172. randomPrefix + randomAlphanumeric(4),
  173. randomPrefix + randomValue.substring(1),
  174. randomPrefix + randomValue.substring(2),
  175. randomPrefix + randomValue.substring(3)
  176. };
  177. JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
  178. for (String optionOverride : optionOverrides) {
  179. underTest.add(optionOverride.toUpperCase(Locale.ENGLISH));
  180. }
  181. }
  182. @Test
  183. public void add_accepts_property_equal_to_mandatory_option_and_does_not_add_it_twice() {
  184. JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
  185. underTest.add(randomPrefix + randomValue);
  186. assertThat(underTest.getAll()).containsOnly(randomPrefix + randomValue);
  187. }
  188. @Test
  189. public void addFromMandatoryProperty_fails_with_IAE_if_property_does_not_exist() {
  190. expectMissingPropertyIAE(this.randomPropertyName);
  191. underTest.addFromMandatoryProperty(new Props(properties), this.randomPropertyName);
  192. }
  193. @Test
  194. public void addFromMandatoryProperty_fails_with_IAE_if_property_contains_an_empty_value() {
  195. expectMissingPropertyIAE(this.randomPropertyName);
  196. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  197. }
  198. @Test
  199. @UseDataProvider("variousEmptyStrings")
  200. public void addFromMandatoryProperty_adds_single_option_of_property_with_trimming(String emptyString) {
  201. properties.put(randomPropertyName, emptyString + "-foo" + emptyString);
  202. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  203. assertThat(underTest.getAll()).containsOnly("-foo");
  204. }
  205. @Test
  206. @UseDataProvider("variousEmptyStrings")
  207. public void addFromMandatoryProperty_fails_with_MessageException_if_property_does_not_start_with_dash_after_trimmed(String emptyString) {
  208. properties.put(randomPropertyName, emptyString + "foo -bar");
  209. expectJvmOptionNotEmptyAndStartByDashMessageException(randomPropertyName, "foo");
  210. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  211. }
  212. @Test
  213. @UseDataProvider("variousEmptyStrings")
  214. public void addFromMandatoryProperty_adds_options_of_property_with_trimming(String emptyString) {
  215. properties.put(randomPropertyName, emptyString + "-foo" + emptyString + " -bar" + emptyString + " -duck" + emptyString);
  216. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  217. assertThat(underTest.getAll()).containsOnly("-foo", "-bar", "-duck");
  218. }
  219. @Test
  220. public void addFromMandatoryProperty_supports_spaces_inside_options() {
  221. properties.put(randomPropertyName, "-foo bar -duck");
  222. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  223. assertThat(underTest.getAll()).containsOnly("-foo bar", "-duck");
  224. }
  225. @Test
  226. public void addFromMandatoryProperty_throws_IAE_if_option_starts_with_prefix_of_mandatory_option_but_has_different_value() {
  227. String[] optionOverrides = {
  228. randomPrefix,
  229. randomPrefix + randomValue.substring(1),
  230. randomPrefix + randomValue.substring(1),
  231. randomPrefix + randomValue.substring(2),
  232. randomPrefix + randomValue.substring(3),
  233. randomPrefix + randomValue.substring(3) + randomAlphanumeric(1),
  234. randomPrefix + randomValue.substring(3) + randomAlphanumeric(2),
  235. randomPrefix + randomValue.substring(3) + randomAlphanumeric(3),
  236. randomPrefix + randomValue + randomAlphanumeric(1)
  237. };
  238. JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
  239. for (String optionOverride : optionOverrides) {
  240. try {
  241. properties.put(randomPropertyName, optionOverride);
  242. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  243. fail("an MessageException should have been thrown");
  244. } catch (MessageException e) {
  245. assertThat(e.getMessage())
  246. .isEqualTo("a JVM option can't overwrite mandatory JVM options. " +
  247. "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " + optionOverride + " overwrites " + randomPrefix + randomValue);
  248. }
  249. }
  250. }
  251. @Test
  252. public void addFromMandatoryProperty_checks_against_mandatory_options_is_case_sensitive() {
  253. String[] optionOverrides = {
  254. randomPrefix,
  255. randomPrefix + randomValue.substring(1),
  256. randomPrefix + randomValue.substring(1),
  257. randomPrefix + randomValue.substring(2),
  258. randomPrefix + randomValue.substring(3),
  259. randomPrefix + randomValue.substring(3) + randomAlphanumeric(1),
  260. randomPrefix + randomValue.substring(3) + randomAlphanumeric(2),
  261. randomPrefix + randomValue.substring(3) + randomAlphanumeric(3),
  262. randomPrefix + randomValue + randomAlphanumeric(1)
  263. };
  264. JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
  265. for (String optionOverride : optionOverrides) {
  266. properties.setProperty(randomPropertyName, optionOverride.toUpperCase(Locale.ENGLISH));
  267. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  268. }
  269. }
  270. @Test
  271. public void addFromMandatoryProperty_reports_all_overriding_options_in_single_exception() {
  272. String overriding1 = randomPrefix;
  273. String overriding2 = randomPrefix + randomValue + randomAlphanumeric(1);
  274. properties.setProperty(randomPropertyName, "-foo " + overriding1 + " -bar " + overriding2);
  275. JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
  276. expectedException.expect(MessageException.class);
  277. expectedException.expectMessage("a JVM option can't overwrite mandatory JVM options. " +
  278. "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " +
  279. overriding1 + " overwrites " + randomPrefix + randomValue + ", " + overriding2 + " overwrites " + randomPrefix + randomValue);
  280. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  281. }
  282. @Test
  283. public void addFromMandatoryProperty_accepts_property_equal_to_mandatory_option_and_does_not_add_it_twice() {
  284. JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
  285. properties.put(randomPropertyName, randomPrefix + randomValue);
  286. underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
  287. assertThat(underTest.getAll()).containsOnly(randomPrefix + randomValue);
  288. }
  289. @Test
  290. public void toString_prints_all_jvm_options() {
  291. underTest.add("-foo").add("-bar");
  292. assertThat(underTest.toString()).isEqualTo("[-foo, -bar]");
  293. }
  294. private void expectJvmOptionNotNullNPE() {
  295. expectedException.expect(NullPointerException.class);
  296. expectedException.expectMessage("a JVM option can't be null");
  297. }
  298. private void expectJvmOptionNotEmptyAndStartByDashIAE() {
  299. expectedException.expect(IllegalArgumentException.class);
  300. expectedException.expectMessage("a JVM option can't be empty and must start with '-'");
  301. }
  302. private void expectJvmOptionNotEmptyAndStartByDashMessageException(String randomPropertyName, String option) {
  303. expectedException.expect(MessageException.class);
  304. expectedException.expectMessage("a JVM option can't be empty and must start with '-'. " +
  305. "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " + option);
  306. }
  307. public void expectMissingPropertyIAE(String randomPropertyName) {
  308. expectedException.expect(IllegalArgumentException.class);
  309. expectedException.expectMessage("Missing property: " + randomPropertyName);
  310. }
  311. @DataProvider()
  312. public static Object[][] variousEmptyStrings() {
  313. return new Object[][] {
  314. {""},
  315. {" "},
  316. {" "}
  317. };
  318. }
  319. private static Map<String, String> shuffleThenToMap(Stream<Option> stream) {
  320. List<Option> options = stream.collect(Collectors.toList());
  321. Collections.shuffle(options);
  322. Map<String, String> res = new HashMap<>(options.size());
  323. for (Option option : options) {
  324. res.put(option.getPrefix(), option.getValue());
  325. }
  326. return res;
  327. }
  328. private static final class Option {
  329. private final String prefix;
  330. private final String value;
  331. private Option(String prefix, String value) {
  332. this.prefix = prefix;
  333. this.value = value;
  334. }
  335. public String getPrefix() {
  336. return prefix;
  337. }
  338. public String getValue() {
  339. return value;
  340. }
  341. @Override
  342. public String toString() {
  343. return "[" + prefix + "-" + value + ']';
  344. }
  345. }
  346. }