content: package org.aion.zero.impl.cli;
  
  import static com.google.common.truth.Truth.assertThat;
  import static org.aion.util.TestResources.TEST_RESOURCE_DIR;
  import static org.aion.zero.impl.cli.Cli.ReturnType.ERROR;
  import static org.aion.zero.impl.cli.Cli.ReturnType.EXIT;
  import static org.aion.zero.impl.cli.Cli.ReturnType.RUN;
  import static org.junit.Assert.assertEquals;
  import static org.mockito.ArgumentMatchers.any;
  import static org.mockito.ArgumentMatchers.anyString;
  import static org.mockito.Mockito.doCallRealMethod;
  import static org.mockito.Mockito.doReturn;
  import static org.mockito.Mockito.spy;
  import static org.mockito.Mockito.times;
  import static org.mockito.Mockito.verify;
  
  import com.google.common.base.Preconditions;
  import com.google.common.collect.ImmutableList;
  import com.google.common.io.Files;
  import java.io.File;
  import java.io.IOException;
  import java.nio.file.FileVisitResult;
  import java.nio.file.Path;
  import java.nio.file.SimpleFileVisitor;
  import java.nio.file.attribute.BasicFileAttributes;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.Objects;
  import junitparams.JUnitParamsRunner;
  import junitparams.Parameters;
  import org.aion.crypto.ECKey;
  import org.aion.crypto.ECKeyFac;
  import org.aion.log.LogEnum;
  import org.aion.log.LogLevel;
  import org.aion.zero.impl.keystore.Keystore;
  import org.aion.zero.impl.config.CfgDb;
  import org.aion.util.conversions.Hex;
  import org.aion.zero.impl.cli.Cli.ReturnType;
  import org.aion.zero.impl.cli.Cli.TaskPriority;
  import org.aion.zero.impl.config.CfgAion;
  import org.aion.zero.impl.vm.AvmTestConfig;
  import org.junit.After;
  import org.junit.AfterClass;
  import org.junit.Before;
  import org.junit.BeforeClass;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.mockito.Mockito;
  import picocli.CommandLine;
  import picocli.CommandLine.Model.CommandSpec;
  import picocli.CommandLine.ParseResult;
  
  /** CliTest for new version with use of different networks. */
  @RunWith(JUnitParamsRunner.class)
  public class CliTest {
  
      private final PasswordReader mockpr = Mockito.mock(PasswordReader.class);
      private final Cli cli = new Cli(mockpr);
      private final CfgAion cfg = CfgAion.inst();
      private final Cli mockCli = spy(cli);
  
      // NOTE: Do not add more networks!! Simply update testnet name when the current one is outdated.
      private static final String currentTestNetName = "amity";
  
      // base paths
      private static final String BASE_PATH = System.getProperty("user.dir");
      private static final File MAIN_BASE_PATH = new File(BASE_PATH, "mainnet");
      private static final File TEST_BASE_PATH = new File(BASE_PATH, currentTestNetName);
  
  
      // config paths
      private static final File NETWORKS_PATH = new File(BASE_PATH, "networks");
      private static final File MAIN_NETWORKS_PATH = new File(NETWORKS_PATH, "mainnet");
      private static final File TEST_NETWORKS_PATH = new File(NETWORKS_PATH, currentTestNetName);
  
  
      private static final String module = "modAionImpl";
  
      private static final String configFileName = "config.xml";
      private static final String genesisFileName = "genesis.json";
      private static final String forkFileName = "fork.properties";
  
      private static final String dataDirectory = "datadir";
      private static final String alternativeDirectory = "random";
  
      private static final File path = new File(BASE_PATH, dataDirectory);
      private static final File alternativePath = new File(BASE_PATH, alternativeDirectory);
  
      private static final File config = new File(TEST_RESOURCE_DIR, configFileName);
      private static final File mainnetConfig = new File(MAIN_NETWORKS_PATH, configFileName);
      private static final File testnetConfig = new File(TEST_NETWORKS_PATH, configFileName);
  
  
      private static final File genesis = new File(TEST_RESOURCE_DIR, genesisFileName);
      private static final File mainnetGenesis = new File(MAIN_NETWORKS_PATH, genesisFileName);
      private static final File testnetGenesis = new File(TEST_NETWORKS_PATH, genesisFileName);
  
  
      private static final File fork = new File(TEST_RESOURCE_DIR, forkFileName);
  
      private static final File mainnetFork = new File(MAIN_NETWORKS_PATH, forkFileName);
      private static final File testnetFork = new File(TEST_NETWORKS_PATH, forkFileName);
  
  
      private static final String DEFAULT_PORT = "30303";
      private static final String TEST_PORT = "12345";
      private static final String INVALID_PORT = "123450";
  
      /** @implNote set this to true to enable printing */
      private static final boolean verbose = false;
  
      @Before
      public void setup() {
          // reset config values
          cfg.fromXML(config);
  
          if (BASE_PATH.contains(module) && !mainnetConfig.exists()) {
              // save config to disk at expected location for new kernel
              if (!MAIN_NETWORKS_PATH.exists()) {
                  assertThat(MAIN_NETWORKS_PATH.mkdirs()).isTrue();
              }
              Cli.copyRecursively(config, mainnetConfig);
              Cli.copyRecursively(genesis, mainnetGenesis);
              Cli.copyRecursively(fork, mainnetFork);
          }
  
          if (BASE_PATH.contains(module) && !testnetConfig.exists()) {
              // save config to disk at expected location for new kernel
              if (!TEST_NETWORKS_PATH.exists()) {
                  assertThat(TEST_NETWORKS_PATH.mkdirs()).isTrue();
              }
              Cli.copyRecursively(config, testnetConfig);
              Cli.copyRecursively(genesis, testnetGenesis);
              Cli.copyRecursively(fork, testnetFork);
          }
  
          cfg.resetInternal();
          doReturn("password").when(mockpr).readPassword(any(), any());
          doCallRealMethod().when(mockCli).callAndDoNotInitializeAvm(any(), any());
      }
  
      @After
      public void shutdown() {
          deleteRecursively(path);
          deleteRecursively(alternativePath);
  
          // in case absolute paths are used
          deleteRecursively(cfg.getKeystoreDir());
          deleteRecursively(cfg.getDatabaseDir());
          deleteRecursively(cfg.getLogDir());
  
          // to avoid deleting config for all tests
          if (BASE_PATH.contains(module)) {
              deleteRecursively(NETWORKS_PATH);
          }
  
          deleteRecursively(MAIN_BASE_PATH);
          deleteRecursively(TEST_BASE_PATH);
      }
  
      @BeforeClass
      public static void setupClass() {
          // Initialize the avm since some CLI calls actually require it to be initialized.
          AvmTestConfig.supportOnlyAvmVersion1();
      }
  
      @AfterClass
      public static void resetKeystorePath() {
          // Shutdown the avm.
          AvmTestConfig.clearConfigurations();
          Keystore.initKeystorePath(); // reset the keystore path we've changed in the tests
      }
  
      /**
       * Ensures that the { <i>-h</i>, <i>--help</i>, <i>-v</i>, <i>--version</i> } arguments work.
       */
      @Test
      @Parameters({"-h", "--help", "-v", "--version"})
      public void testHelpAndVersion(String option) {
          assertThat(cli.callAndDoNotInitializeAvm(new String[] {option}, cfg)).isEqualTo(EXIT);
      }
  
      /** Parameters for testing {@link #testDirectoryAndNetwork(String[], ReturnType, String, String)}. */
      @SuppressWarnings("unused")
      private Object parametersWithDirectoryAndNetwork() {
          List<Object> parameters = new ArrayList<>();
  
          String[] dir_options = new String[] {"-d", "--datadir"};
          String[] net_options = new String[] {"-n", "--network"};
          String expectedExec = new File(path, "mainnet").getAbsolutePath();
          String expectedRead = MAIN_NETWORKS_PATH.getAbsolutePath();
          String expOnError = MAIN_BASE_PATH.getAbsolutePath();
  
          // data directory alone
          for (String op : dir_options) {
              // with relative path
              parameters.add(new Object[] {new String[] {op, dataDirectory}, RUN, expectedExec, expectedRead});
              // with absolute path
              parameters.add(new Object[] {new String[] {op, path.getAbsolutePath()}, RUN, expectedExec, expectedRead});
              // without value
              parameters.add(new Object[] {new String[] {op}, ERROR, expOnError, expectedRead});
              // with invalid characters (Linux & Win)
              parameters.add(new Object[] {new String[] {op, "/\\<>:\"|?*"}, ERROR, expOnError, expectedRead});
          }
  
          // network and directory
          String[] net_values = new String[] {"mainnet", "invalid"};
          for (String opDir : dir_options) {
              for (String opNet : net_options) {
                  for (String valNet : net_values) {
                      // with relative path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, dataDirectory, opNet, valNet}, RUN, expectedExec, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, valNet, opDir, dataDirectory}, RUN, expectedExec, expectedRead
                              });
                      // with absolute path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, path.getAbsolutePath(), opNet, valNet},
                                  RUN,
                                  expectedExec, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, valNet, opDir, path.getAbsolutePath()},
                                  RUN,
                                  expectedExec, expectedRead
                              });
                  }
              }
          }
  
          // network alone
          expectedExec = MAIN_BASE_PATH.getAbsolutePath();
          for (String op : net_options) {
              // without parameter
              parameters.add(new Object[] {new String[] {op}, ERROR, expOnError, expectedRead});
              // with two parameters
              parameters.add(new Object[] {new String[] {op, "testnet", currentTestNetName}, ERROR, expOnError, expectedRead});
              // invalid parameter
              parameters.add(new Object[] {new String[] {op, "invalid"}, RUN, expectedExec, expectedRead});
              // mainnet as parameter
              parameters.add(new Object[] {new String[] {op, "mainnet"}, RUN, expectedExec, expectedRead});
          }
  
          // network alone with testnet
          net_values = new String[] {currentTestNetName, "testnet"};
          expectedExec = TEST_BASE_PATH.getAbsolutePath();
          expectedRead = TEST_NETWORKS_PATH.getAbsolutePath();
          for (String op : net_options) {
              for (String netVal : net_values) {
                  // testnet name as parameter
                  parameters.add(new Object[] {new String[] {op, netVal}, RUN, expectedExec, expectedRead});
              }
          }
  
          // network and directory with testnet
          expectedExec = new File(path, currentTestNetName).getAbsolutePath();
          for (String opDir : dir_options) {
              for (String opNet : net_options) {
                  for (String netVal : net_values) {
                      // with relative path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, dataDirectory, opNet, netVal}, RUN, expectedExec, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, netVal, opDir, dataDirectory}, RUN, expectedExec, expectedRead
                              });
                      // with absolute path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, path.getAbsolutePath(), opNet, netVal},
                                  RUN,
                                  expectedExec, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, netVal, opDir, path.getAbsolutePath()},
                                  RUN,
                                  expectedExec, expectedRead
                              });
                  }
              }
          }
  
          // with subdirectories
          String dir = dataDirectory + File.separator + "subfolder";
          File path = new File(BASE_PATH, dir);
          expectedExec = new File(path, "mainnet").getAbsolutePath();
          expectedRead = MAIN_NETWORKS_PATH.getAbsolutePath();
          for (String op : dir_options) {
              // with relative path with subdirectories
              parameters.add(new Object[] {new String[] {op, dir}, RUN, expectedExec, expectedRead});
              // with multiple values
              parameters.add(new Object[] {new String[] {op, dataDirectory, dir}, ERROR, expOnError, expectedRead});
          }
  
          return parameters.toArray();
      }
  
      /**
       * Ensures that the { <i>-d</i>, <i>--datadir</i>, <i>-n</i>, <i>--network</i> } arguments work.
       */
      @Test
      @Parameters(method = "parametersWithDirectoryAndNetwork")
      public void testDirectoryAndNetwork(String[] input, ReturnType expectedReturn, String expectedExecPath, String expectedReadPath) {
          assertThat(cli.callAndDoNotInitializeAvm(input, cfg)).isEqualTo(expectedReturn);
          assertThat(cfg.getBasePath()).isEqualTo(expectedExecPath);
          assertThat(cfg.getExecConfigFile()).isEqualTo(new File(expectedExecPath, "config" + File.separator + configFileName));
          assertThat(cfg.getGenesisFile()).isEqualTo(new File(expectedReadPath, genesisFileName));
          assertThat(cfg.getForkFile()).isEqualTo(new File(expectedReadPath, forkFileName));
          assertThat(cfg.getDatabaseDir()).isEqualTo(new File(expectedExecPath, "database"));
          assertThat(cfg.getLogDir()).isEqualTo(new File(expectedExecPath, "log"));
          assertThat(cfg.getKeystoreDir()).isEqualTo(new File(expectedExecPath, "keystore"));
  
          if (verbose) {
              printPaths(cfg);
          }
      }
  
      private void printPaths(CfgAion cfg) {
          System.out.println(
                  "\n-------------------------------- USED PATHS --------------------------------"
                          + "\n> Logger path:   "
                          + cfg.getLogDir().getAbsolutePath()
                          + "\n> Database path: "
                          + cfg.getDatabaseDir().getAbsolutePath()
                          + "\n> Keystore path: "
                          + cfg.getKeystoreDir().getAbsolutePath()
                          + "\n> Config write:  "
                          + cfg.getExecConfigFile().getAbsolutePath()
                          + "\n----------------------------------------------------------------------------"
                          + "\n> Config read:   "
                          + cfg.getInitialConfigFile().getAbsolutePath()
                          + "\n> Genesis read:  "
                          + cfg.getGenesisFile().getAbsolutePath()
                          + "\n> Fork read:  "
                          + cfg.getForkFile().getAbsolutePath()
                          + "\n----------------------------------------------------------------------------\n\n");
      }
  
      /**
       * Ensures that the { <i>-d</i>, <i>--datadir</i>, <i>-n</i>, <i>--network</i> } arguments work
       * with absolute paths for the database and log. The absolute path overwrites the datadir option
       * location.
       */
      @Test
      @Parameters(method = "parametersWithDirectoryAndNetwork")
      public void testDirectoryAndNetwork_wAbsoluteDbAndLogPath(String[] input, ReturnType expectedReturn, String expectedExecPath, String expectedReadPath) {
  
          File db = new File(alternativePath, "database");
          cfg.getDb().setPath(db.getAbsolutePath());
          File log = new File(alternativePath, "log");
          cfg.getLog().setLogPath(log.getAbsolutePath());
  
          // save and reload for changes to take effect
          cfg.toXML(null, cfg.getInitialConfigFile());
          cfg.fromXML();
  
          assertThat(cli.callAndDoNotInitializeAvm(input, cfg)).isEqualTo(expectedReturn);
          assertThat(cfg.getBasePath()).isEqualTo(expectedExecPath);
          assertThat(cfg.getExecConfigFile()).isEqualTo(new File(expectedExecPath, "config" + File.separator + configFileName));
          assertThat(cfg.getGenesisFile()).isEqualTo(new File(expectedReadPath, genesisFileName));
          assertThat(cfg.getForkFile()).isEqualTo(new File(expectedReadPath, forkFileName));
  
          assertThat(cfg.getDatabaseDir()).isNotEqualTo(new File(expectedExecPath, "database"));
          assertThat(cfg.getDatabaseDir()).isEqualTo(db);
  
          assertThat(cfg.getLogDir()).isNotEqualTo(new File(expectedExecPath, "log"));
          assertThat(cfg.getLogDir()).isEqualTo(log);
  
          if (verbose) {
              printPaths(cfg);
          }
      }
  
      /** Parameters for testing {@link #testConfig(String[], File, String, String)}. */
      @SuppressWarnings("unused")
      private Object parametersWithConfig() {
          List<Object> parameters = new ArrayList<>();
  
          String[] options = new String[] {"-c", "--config"};
          String expected = MAIN_BASE_PATH.getAbsolutePath();
          String expectedRead = MAIN_NETWORKS_PATH.getAbsolutePath();
  
          for (String op : options) {
              // without parameter
              parameters.add(new Object[] {new String[] {op}, mainnetConfig, expected, expectedRead});
              // invalid parameter
              parameters.add(new Object[] {new String[] {op, "invalid"}, mainnetConfig, expected, expectedRead});
              // mainnet as parameter
              parameters.add(new Object[] {new String[] {op, "mainnet"}, mainnetConfig, expected, expectedRead});
          }
  
          expected = TEST_BASE_PATH.getAbsolutePath();
          expectedRead = TEST_NETWORKS_PATH.getAbsolutePath();
  
          for (String op : options) {
              // testnet name as parameter
              parameters.add(new Object[] {new String[] {op, currentTestNetName}, testnetConfig, expected, expectedRead});
              // testnet as parameter
              parameters.add(new Object[] {new String[] {op, "testnet"}, testnetConfig, expected, expectedRead});
          }
  
          // config and directory
          String[] dir_options = new String[] {"-d", "--datadir"};
          File config =
                  new File(
                          path,
                          "mainnet" + File.separator + "config" + File.separator + configFileName);
          expected = new File(path, "mainnet").getAbsolutePath();
          expectedRead = MAIN_NETWORKS_PATH.getAbsolutePath();
  
          String[] net_values = new String[] {"mainnet", "invalid"};
          for (String opDir : dir_options) {
              for (String opCfg : options) {
                  for (String valNet : net_values) {
                      // with relative path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, dataDirectory, opCfg, valNet}, config, expected, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opCfg, valNet, opDir, dataDirectory}, config, expected, expectedRead
                              });
                      // with absolute path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, path.getAbsolutePath(), opCfg, valNet},
                                  config,
                                  expected, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opCfg, valNet, opDir, path.getAbsolutePath()},
                                  config,
                                  expected, expectedRead
                              });
                  }
              }
          }
  
          // config and directory with testnet
          net_values = new String[] {currentTestNetName, "testnet"};
          config = new File(path, currentTestNetName + File.separator + "config" + File.separator + configFileName);
          expected = new File(path, currentTestNetName).getAbsolutePath();
          expectedRead = TEST_NETWORKS_PATH.getAbsolutePath();
          for (String opDir : dir_options) {
              for (String opCfg : options) {
                  for (String netVal : net_values) {
                      // with relative path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, dataDirectory, opCfg, netVal}, config, expected, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opCfg, netVal, opDir, dataDirectory}, config, expected, expectedRead
                              });
                      // with absolute path
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, path.getAbsolutePath(), opCfg, netVal},
                                  config,
                                  expected, expectedRead
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opCfg, netVal, opDir, path.getAbsolutePath()},
                                  config,
                                  expected, expectedRead
                              });
                  }
              }
          }
  
          return parameters.toArray();
      }
  
      /** Ensures that the { <i>-c</i>, <i>--config</i> } arguments work. */
      @Test
      @Parameters(method = "parametersWithConfig")
      public void testConfig(String[] input, File expectedFile, String expectedExecPath, String expectedReadPath) {
          if (expectedFile.exists()) {
              assertThat(cfg.fromXML(expectedFile)).isTrue();
          }
  
          assertThat(cli.callAndDoNotInitializeAvm(input, cfg)).isEqualTo(EXIT);
          assertThat(cfg.getBasePath()).isEqualTo(expectedExecPath);
          assertThat(cfg.getExecConfigFile())
                  .isEqualTo(new File(expectedExecPath, "config" + File.separator + configFileName));
          assertThat(cfg.getGenesisFile()).isEqualTo(new File(expectedReadPath, genesisFileName));
          assertThat(cfg.getForkFile()).isEqualTo(new File(expectedReadPath, forkFileName));
  
          assertThat(expectedFile.exists()).isTrue();
          assertThat(cfg.fromXML(expectedFile)).isFalse();
  
          if (verbose) {
              printPaths(cfg);
          }
      }
  
      /** Parameters for testing {@link #testPort(String[], ReturnType, String, String, String)}. */
      @SuppressWarnings("unused")
      private Object parametersWithPort() {
          List<Object> parameters = new ArrayList<>();
  
          String[] portOptions = new String[] {"-p", "--port"};
          String[] netOptions = new String[] {"-n", "--network"};
          String[] dirOptions = new String[] {"-d", "--datadir"};
          String expectedExecPath = MAIN_BASE_PATH.getAbsolutePath();
          String expectedReadPath = MAIN_NETWORKS_PATH.getAbsolutePath();
          String expPathOnError = MAIN_BASE_PATH.getAbsolutePath();
          String expPortOnError = Integer.toString(cfg.getNet().getP2p().getPort());
  
          // port alone
          for (String opPort : portOptions) {
              // without parameter
              parameters.add(
                      new Object[] {new String[] {opPort}, ERROR, expPathOnError, expectedReadPath, expPortOnError});
              // with two parameters
              parameters.add(
                      new Object[] {
                          new String[] {opPort, TEST_PORT, TEST_PORT},
                          ERROR,
                          expPathOnError, expectedReadPath,
                          expPortOnError
                      });
              // with invalid parameter
              parameters.add(
                      new Object[] {
                          new String[] {opPort, INVALID_PORT}, RUN, expPathOnError, expectedReadPath, expPortOnError
                      });
              parameters.add(
                      new Object[] {
                          new String[] {opPort, "-12345"}, RUN, expPathOnError, expectedReadPath, expPortOnError
                      });
              parameters.add(
                      new Object[] {
                          new String[] {opPort, "invalid"}, RUN, expPathOnError, expectedReadPath, expPortOnError
                      });
              // with testing port number
              parameters.add(
                      new Object[] {new String[] {opPort, TEST_PORT}, RUN, expectedExecPath, expectedReadPath, TEST_PORT});
          }
  
          // port with help and version
          for (String opPort : portOptions) {
              parameters.add(
                      new Object[] {
                          new String[] {opPort, TEST_PORT, "-h"}, EXIT, expectedExecPath, expectedReadPath, expPortOnError
                      });
              parameters.add(
                      new Object[] {
                          new String[] {opPort, TEST_PORT, "-v"}, EXIT, expectedExecPath, expectedReadPath, expPortOnError
                      });
          }
  
          // network and port
          String[] netValues = new String[] {"mainnet", "invalid"};
          for (String opNet : netOptions) {
              for (String valNet : netValues) {
                  for (String opPort : portOptions) {
                      // without port parameter
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, valNet, opPort},
                                  ERROR,
                                  expPathOnError, expectedReadPath,
                                  expPortOnError
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opPort, opNet, valNet},
                                  ERROR,
                                  expPathOnError, expectedReadPath,
                                  expPortOnError
                              });
                      // with invalid port parameter
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, valNet, opPort, INVALID_PORT},
                                  RUN,
                                  expPathOnError, expectedReadPath,
                                  expPortOnError
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opPort, INVALID_PORT, opNet, valNet},
                                  RUN,
                                  expPathOnError, expectedReadPath,
                                  expPortOnError
                              });
                      // with testing port number
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, valNet, opPort, TEST_PORT},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  TEST_PORT
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opPort, TEST_PORT, opNet, valNet},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  TEST_PORT
                              });
                  }
              }
          }
  
          // network and port with testnet
          netValues = new String[] {currentTestNetName, "testnet"};
          expectedExecPath = TEST_BASE_PATH.getAbsolutePath();
          expectedReadPath = TEST_NETWORKS_PATH.getAbsolutePath();
          for (String opNet : netOptions) {
              for (String valNet : netValues) {
                  for (String opPort : portOptions) {
                      parameters.add(
                              new Object[] {
                                  new String[] {opNet, valNet, opPort, TEST_PORT},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  TEST_PORT
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opPort, TEST_PORT, opNet, valNet},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  TEST_PORT
                              });
                  }
              }
          }
  
          // directory and port
          String[] dirValues = new String[] {dataDirectory, path.getAbsolutePath()};
          expectedExecPath = new File(path, "mainnet").getAbsolutePath();
          expectedReadPath = MAIN_NETWORKS_PATH.getAbsolutePath();
          for (String opDir : dirOptions) {
              for (String valDir : dirValues) {
                  for (String opPort : portOptions) {
                      // without port parameter
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, valDir, opPort},
                                  ERROR,
                                  expPathOnError, expectedReadPath,
                                  expPortOnError
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opPort, opDir, valDir},
                                  ERROR,
                                  expPathOnError, expectedReadPath,
                                  expPortOnError
                              });
                      // with invalid port parameter
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, valDir, opPort, INVALID_PORT},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  expPortOnError
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opPort, INVALID_PORT, opDir, valDir},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  expPortOnError
                              });
                      // with testing port number
                      parameters.add(
                              new Object[] {
                                  new String[] {opDir, valDir, opPort, TEST_PORT},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  TEST_PORT
                              });
                      parameters.add(
                              new Object[] {
                                  new String[] {opPort, TEST_PORT, opDir, valDir},
                                  RUN,
                                  expectedExecPath, expectedReadPath,
                                  TEST_PORT
                              });
                  }
              }
          }
  
          // network, directory and port
          netValues = new String[] {"mainnet", currentTestNetName};
          for (String opNet : netOptions) {
              for (String valNet : netValues) {
                  if (valNet == currentTestNetName) {
                      expectedReadPath = TEST_NETWORKS_PATH.getAbsolutePath();
                  } else {
                      expectedReadPath = MAIN_NETWORKS_PATH.getAbsolutePath();
                  }
                  for (String opDir : dirOptions) {
                      for (String valDir : dirValues) {
                          for (String opPort : portOptions) {
                              expectedExecPath = new File(path, valNet).getAbsolutePath();
                              parameters.add(
                                      new Object[] {
                                          new String[] {
                                              opNet, valNet, opDir, valDir, opPort, TEST_PORT
                                          },
                                          RUN,
                                          expectedExecPath, expectedReadPath,
                                          TEST_PORT
                                      });
                          }
                      }
                  }
              }
          }
  
          // directory with subdirectories and port
          String dir = dataDirectory + File.separator + "subfolder";
          File testPath = new File(BASE_PATH, dir);
          expectedExecPath = new File(testPath, "mainnet").getAbsolutePath();
          expectedReadPath = MAIN_NETWORKS_PATH.getAbsolutePath();
          for (String opDir : dirOptions) {
              for (String opPort : portOptions) {
                  // with relative path with subdirectories
                  parameters.add(
                          new Object[] {
                              new String[] {opDir, dir, opPort, TEST_PORT},
                              RUN,
                              expectedExecPath, expectedReadPath,
                              TEST_PORT
                          });
              }
          }
  
          // port with config and directory
          expectedExecPath = new File(path, "mainnet").getAbsolutePath();
          for (String opPort : portOptions) {
              // with relative path
              parameters.add(
                      new Object[] {
                          new String[] {
                              "--datadir", dataDirectory, "--config", "mainnet", opPort, TEST_PORT
                          },
                          EXIT,
                          expectedExecPath, expectedReadPath,
                          TEST_PORT
                      });
              parameters.add(
                      new Object[] {
                          new String[] {
                              "-c", "mainnet", opPort, TEST_PORT, "--datadir", dataDirectory
                          },
                          EXIT,
                          expectedExecPath, expectedReadPath,
                          TEST_PORT
                      });
              // with absolute path
              parameters.add(
                      new Object[] {
                          new String[] {
                              opPort, TEST_PORT, "-d", path.getAbsolutePath(), "--config", "mainnet"
                          },
                          EXIT,
                          expectedExecPath, expectedReadPath,
                          TEST_PORT
                      });
              parameters.add(
                      new Object[] {
                          new String[] {
                              "-c", "mainnet", "-d", path.getAbsolutePath(), opPort, TEST_PORT
                          },
                          EXIT,
                          expectedExecPath, expectedReadPath,
                          TEST_PORT
                      });
          }
  
          return parameters.toArray();
      }
  
      @Test
      @Parameters(method = "parametersWithPort")
      public void testPort(String[] input, ReturnType expectedReturn, String expectedExecPath, String expectedReadPath, String expectedPort) {
  
          cfg.toXML(new String[] {"--p2p=" + "," + DEFAULT_PORT}, cfg.getInitialConfigFile());
  
          assertThat(cli.callAndDoNotInitializeAvm(input, cfg)).isEqualTo(expectedReturn);
          assertThat(cfg.getBasePath()).isEqualTo(expectedExecPath);
          assertThat(cfg.getExecConfigFile()).isEqualTo(new File(expectedExecPath, "config" + File.separator + configFileName));
          assertThat(cfg.getGenesisFile()).isEqualTo(new File(expectedReadPath, genesisFileName));
          assertThat(cfg.getForkFile()).isEqualTo(new File(expectedReadPath, forkFileName));
          assertThat(cfg.getDatabaseDir()).isEqualTo(new File(expectedExecPath, "database"));
          assertThat(cfg.getLogDir()).isEqualTo(new File(expectedExecPath, "log"));
          assertThat(cfg.getKeystoreDir()).isEqualTo(new File(expectedExecPath, "keystore"));
          // test port is updated
          assertThat(Integer.toString(cfg.getNet().getP2p().getPort())).isEqualTo(expectedPort);
          // test port in initial config is unchanged
          cfg.resetInternal();
          cfg.fromXML();
          assertThat(Integer.toString(cfg.getNet().getP2p().getPort())).isEqualTo(DEFAULT_PORT);
  
          if (verbose) {
              printPaths(cfg);
          }
      }
  
      /** Parameters for testing {@link #testInfo(String[], ReturnType, String)}. */
      @SuppressWarnings("unused")
      private Object parametersWithInfo() {
          List<Object> parameters = new ArrayList<>();
  
          String[] options = new String[] {"-i", "--info"};
          String expected = MAIN_BASE_PATH.getAbsolutePath();
          String expOnError = expected;
  
          // only info
          for (String op : options) {
              // without parameter
              parameters.add(new Object[] {new String[] {op}, EXIT, expected});
              // invalid parameter
              parameters.add(new Object[] {new String[] {op, "value"}, ERROR, expOnError});
          }
  
          // with network
          expected = TEST_BASE_PATH.getAbsolutePath();
          for (String op : options) {
              // testnet name as parameter
              parameters.add(new Object[] {new String[] {op, "-n", currentTestNetName}, EXIT, expected});
              parameters.add(new Object[] {new String[] {"-n", currentTestNetName, op}, EXIT, expected});
              // invalid parameter
              parameters.add(new Object[] {new String[] {op, "value", "-n", currentTestNetName}, ERROR, expOnError});
          }
  
          // with directory
          expected = new File(path, "mainnet").getAbsolutePath();
          for (String op : options) {
              // with relative path
              parameters.add(new Object[] {new String[] {op, "-d", dataDirectory}, EXIT, expected});
              parameters.add(new Object[] {new String[] {"-d", dataDirectory, op}, EXIT, expected});
              // + invalid parameter
              parameters.add(
                      new Object[] {
                          new String[] {op, "value", "-d", dataDirectory}, ERROR, expOnError
                      });
              // with absolute path
              parameters.add(
                      new Object[] {new String[] {op, "-d", path.getAbsolutePath()}, EXIT, expected});
              parameters.add(
                      new Object[] {new String[] {"-d", path.getAbsolutePath(), op}, EXIT, expected});
              // + invalid parameter
              parameters.add(
                      new Object[] {
                          new String[] {op, "value", "-d", path.getAbsolutePath()}, ERROR, expOnError
                      });
          }
  
          // with port
          expected = MAIN_BASE_PATH.getAbsolutePath();
          for (String op : options) {
              // test port number as parameter
              parameters.add(new Object[] {new String[] {op, "-p", TEST_PORT}, EXIT, expected});
              parameters.add(new Object[] {new String[] {"-p", TEST_PORT, op}, EXIT, expected});
              // invalid port parameter
              parameters.add(new Object[] {new String[] {op, "-p", INVALID_PORT}, EXIT, expOnError});
          }
  
          // with port and directory
          expected = new File(path, "mainnet").getAbsolutePath();
          for (String op : options) {
              // with relative path
              parameters.add(
                      new Object[] {
                          new String[] {op, "-d", dataDirectory, "-p", TEST_PORT}, EXIT, expected
                      });
              // with absolute path
              parameters.add(
                      new Object[] {
                          new String[] {op, "-p", TEST_PORT, "-d", path.getAbsolutePath()},
                          EXIT,
                          expected
                      });
          }
  
          // with network and directory
          expected = new File(path, currentTestNetName).getAbsolutePath();
  
          for (String op : options) {
              // with relative path
              parameters.add(
                      new Object[] {
                          new String[] {op, "-d", dataDirectory, "-n", currentTestNetName}, EXIT, expected
                      });
              parameters.add(
                      new Object[] {
                          new String[] {"-n", currentTestNetName, op, "-d", dataDirectory}, EXIT, expected
                      });
              parameters.add(
                      new Object[] {
                          new String[] {"-n", currentTestNetName, "-d", dataDirectory, op}, EXIT, expected
                      });
              // with absolute path
              parameters.add(
                      new Object[] {
                          new String[] {op, "-n", currentTestNetName, "-d", path.getAbsolutePath()},
                          EXIT,
                          expected
                      });
              parameters.add(
                      new Object[] {
                          new String[] {"-d", path.getAbsolutePath(), op, "-n", currentTestNetName},
                          EXIT,
                          expected
                      });
              parameters.add(
                      new Object[] {
                          new String[] {"-d", path.getAbsolutePath(), "-n", currentTestNetName, op},
                          EXIT,
                          expected
                      });
          }
  
          return parameters.toArray();
      }
  
      /** Ensures that the { <i>-i</i>, <i>--info</i> } arguments work. */
      @Test
      @Parameters(method = "parametersWithInfo")
      public void testInfo(String[] input, ReturnType expectedReturn, String expectedPath) {
          assertThat(cli.callAndDoNotInitializeAvm(input, cfg)).isEqualTo(expectedReturn);
          assertThat(cfg.getBasePath()).isEqualTo(expectedPath);
      }
  
      /**
       * Ensures that the { <i>-i</i>, <i>--info</i> } arguments work when using the config location
       * copied to the execution directory.
       */
      @Test
      @Parameters({"-i", "--info"})
      public void testInfo_execLocation(String option) {
          // generates the config at the required destination
          cli.callAndDoNotInitializeAvm(new String[] {"-c", "-d", dataDirectory}, cfg);
  
          // ensure config exists on disk at expected location for old kernel
          assertThat(cfg.getExecConfigFile().exists()).isTrue();
  
          assertThat(cli.callAndDoNotInitializeAvm(new String[] {option, "-d", dataDirectory}, cfg)).isEqualTo(EXIT);
      }
  
      private List<Object> parametersWithAccount(String[] options) {
          List<Object> parameters = new ArrayList<>();
  
          // only info
          for (String op : options) {
              String[] params = op.split("\\s");
              String param1 =params[0];
              String param2 =params[1];
  
                  // without parameter
              parameters.add(new Object[] {params, EXIT});
              // invalid parameter
              parameters.add(new Object[] {new String[] {param1, param2, "value"}, ERROR});
          }
  
          // with network
          for (String op : options) {
              String[] params = op.split("\\s");
              String param1 =params[0];
              String param2 =params[1];
              // testnet name as parameter
              parameters.add(new Object[] {new String[] {param1, param2, "-n", currentTestNetName}, ERROR});
              parameters.add(new Object[] {new String[] {"-n", currentTestNetName, param1, param2}, EXIT});
              // invalid parameter
              parameters.add(new Object[] {new String[] {param1, param2, "value", "-n", currentTestNetName}, ERROR});
          }
  
          // with directory
          for (String op : options) {
              String[] params = op.split("\\s");
              String param1 =params[0];
              String param2 =params[1];
              // with relative path
              parameters.add(new Object[] {new String[] {param1, param2, "-d", dataDirectory}, ERROR});
              parameters.add(new Object[] {new String[] {"-d", dataDirectory, param1, param2}, EXIT});
              // + invalid parameter
              parameters.add(new Object[] {new String[] {param1, param2, "value", "-d", dataDirectory}, ERROR});
              // with absolute path
              parameters.add(new Object[] {new String[] {param1, param2, "-d", path.getAbsolutePath()}, ERROR});
              parameters.add(new Object[] {new String[] {"-d", path.getAbsolutePath(), param1, param2}, EXIT});
              // + invalid parameter
              parameters.add(
                      new Object[] {new String[] {param1, param2, "value", "-d", path.getAbsolutePath()}, ERROR});
          }
  
          // with network and directory
          for (String op : options) {
              String[] params = op.split("\\s");
              String param1 =params[0];
              String param2 =params[1];
              // with relative path
              parameters.add(new Object[] {new String[] {param1, param2, "-d", dataDirectory, "-n", currentTestNetName}, ERROR});
              parameters.add(new Object[] {new String[] {"-n", currentTestNetName, param1, param2, "-d", dataDirectory}, ERROR});
              parameters.add(new Object[] {new String[] {"-n", currentTestNetName, "-d", dataDirectory, param1, param2}, EXIT});
              // with absolute path
              parameters.add(
                      new Object[] {
                          new String[] {param1, param2, "-n", currentTestNetName, "-d", path.getAbsolutePath()}, ERROR
                      });
              parameters.add(
                      new Object[] {
                          new String[] {"-d", path.getAbsolutePath(), param1, param2, "-n", currentTestNetName}, ERROR
                      });
              parameters.add(
                      new Object[] {
                          new String[] {"-d", path.getAbsolutePath(), "-n", currentTestNetName, param1, param2}, EXIT
                      });
          }
  
          return parameters;
      }
  
      /** Parameters for testing {@link #testCreateAccount(String[], ReturnType)}. */
      @SuppressWarnings("unused")
      private Object parametersWithCreateAccount() {
          return parametersWithAccount(new String[] {"a create", "a c", "account create","account c"})
                  .toArray();
      }
  
      /** Ensures that the { <i>-a create</i>, <i>ac</i> } arguments work. */
      @Test
      @Parameters(method = "parametersWithCreateAccount")
      public void testCreateAccount(String[] input, ReturnType expectedReturn) {
          // number of accounts before create call
          int count = Keystore.list().length;
          assertThat(mockCli.callAndDoNotInitializeAvm(input, cfg)).isEqualTo(expectedReturn);
          if (expectedReturn == EXIT) {
              // ensure number of accounts was incremented
              assertThat(Keystore.list().length).isEqualTo(count + 1);
          } else {
              // ensure number of accounts is unchanged
              assertThat(Keystore.list().length).isEqualTo(count);
          }
      }
  
  
      // TODO: add test for create account with old config location
  
      /** Parameters for testing {@link #testListAccounts(String[], ReturnType)}. */
      @SuppressWarnings("unused")
      private Object parametersWithListAccount() {
          return parametersWithAccount(new String[] {"a list", "a l", "account l","account list"}).toArray();
      }
  
      /** Ensures that the { <i>-a list</i>, <i>al</i> } arguments work. */
      @Test
      @Parameters(method = "parametersWithListAccount")
      public void testListAccounts(String[] input, ReturnType expectedReturn) {
          assertThat(cli.callAndDoNotInitializeAvm(input, cfg)).isEqualTo(expectedReturn);
      }
  
      /** Ensures that the { <i>-a export</i>, <i>ae</i>, <i>--account export</i> } arguments work. */
      @Test
      @Parameters({"a|export", "a|e", "account|export", "account|e"})
      public void testExportPrivateKey(String command, String option) {
          // create account
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {"a","c"}, cfg)).isEqualTo(EXIT);
  
          String account = Keystore.list()[0];
          assertEquals(EXIT, mockCli.callAndDoNotInitializeAvm(new String[] {command, option, account}, cfg));
      }
  
      /**
       * Ensures that the { <i>-a export</i>, <i>ae</i>, <i>--account export</i> } arguments work in
       * combination with the data directory option.
       */
      @Test
      @Parameters({"a|export", "a|e", "account|export","account|export"})
      public void testExportPrivateKey_withDataDir(String command, String option) {
          // create account
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {"-d", dataDirectory, "a","c"}, cfg)).isEqualTo(EXIT);
  
          String account = Keystore.list()[0];
          assertEquals(ERROR, mockCli.callAndDoNotInitializeAvm(new String[] {command, option, "-d", dataDirectory}, cfg));
          assertEquals(EXIT, mockCli.callAndDoNotInitializeAvm(new String[] {"-d", dataDirectory, command, option, account}, cfg));
      }
  
      /**
       * Ensures that the { <i>-a export</i>, <i>ae</i>, <i>--account export</i> } arguments work in
       * combination with the network option.
       */
      @Test
      @Parameters({"a|export", "a|e", "account|export","account|e"})
      public void testExportPrivateKey_withNetwork(String command, String option) {
          // create account
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName,"a","c"}, cfg)).isEqualTo(EXIT);
  
          String account = Keystore.list()[0];
          assertEquals(ERROR, mockCli.callAndDoNotInitializeAvm(new String[] {command, option, account, "-n", currentTestNetName}, cfg));
          assertEquals(EXIT, mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName, command, option, account}, cfg));
      }
  
      /**
       * Ensures that the { <i>-a export</i>, <i>ae</i>, <i>--account export</i> } arguments work in
       * combination with the data directory and network options.
       */
      @Test
      @Parameters({"a|export", "a|e", "account|export", "account|e"})
      public void testExportPrivateKey_withDataDirAndNetwork(String command, String option) {
          // create account
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {"-d", dataDirectory, "-n", currentTestNetName ,"a","c" }, cfg))
                  .isEqualTo(EXIT);
  
          String account = Keystore.list()[0];
          assertEquals(
                  ERROR,
                  mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName, command, option, account, "-d", dataDirectory}, cfg));
          assertEquals(
                  EXIT,
                  mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName, "-d", dataDirectory, command, option, account}, cfg));
          assertEquals(
                  ERROR,
                  mockCli.callAndDoNotInitializeAvm(new String[] {command, option, account, "-d", dataDirectory, "-n", currentTestNetName}, cfg));
      }
  
      /**
       * Ensured that the { <i>-a export</i>, <i>ae</i>, <i>--account export</i> } arguments fail when
       * the given an incorrect account value (proper substring of a valid account).
       */
      @Test
      @Parameters({"a|export", "a|e", "account|export","account|e"})
      public void testExportPrivateKey_wSubstringOfAccount(String command, String option) {
          // create account
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {"a","c"}, cfg)).isEqualTo(EXIT);
  
          String subStrAcc = Keystore.list()[0].substring(1);
  
          assertEquals(ERROR, cli.callAndDoNotInitializeAvm(new String[] {command, option, subStrAcc}, cfg));
      }
  
      /** Ensures that the { <i>-a import</i>, <i>a</i>, <i>--account import</i> } arguments work. */
      @Test
      @Parameters({"a|import", "a|i", "account|import","account|i"})
      public void testImportPrivateKey(String command ,String option) {
          // test 1: successful call
          ECKey key = ECKeyFac.inst().create();
          String pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(EXIT, mockCli.callAndDoNotInitializeAvm(new String[] {command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
  
          // test 2: error -> known key
          assertEquals(ERROR, mockCli.callAndDoNotInitializeAvm(new String[] {command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
      }
  
      /**
       * Ensures that the { <i>-a import</i>, <i>a</i>, <i>--account import</i> } arguments work in
       * combination with the data directory option.
       */
      @Test
      @Parameters({"a|import", "a|i", "account|import","account|i"})
      public void testImportPrivateKey_withDataDir(String command, String option) {
          // test 1: -d last
          ECKey key = ECKeyFac.inst().create();
          String pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(ERROR, mockCli.callAndDoNotInitializeAvm(new String[] {command, option, pKey, "-d", dataDirectory}, cfg));
          assertThat(Keystore.list().length).isEqualTo(0);
  
          // test 2: -d first
          key = ECKeyFac.inst().create();
          pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(EXIT, mockCli.callAndDoNotInitializeAvm(new String[] {"-d", dataDirectory, command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
  
          // test 3: known key
          assertEquals(ERROR, mockCli.callAndDoNotInitializeAvm(new String[] {"-d", dataDirectory, command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
      }
  
      /**
       * Ensures that the { <i>-a import</i>, <i>a</i>, <i>--account import</i> } arguments work in
       * combination with the network option.
       */
      @Test
      @Parameters({"a|import", "a|i", "account|import","account|i"})
      public void testImportPrivateKey_withNetwork(String command, String option) {
          // test 1: -n last
          ECKey key = ECKeyFac.inst().create();
          String pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(ERROR, mockCli.callAndDoNotInitializeAvm(new String[] {command, option, pKey, "-n", currentTestNetName}, cfg));
          assertThat(Keystore.list().length).isEqualTo(0);
  
          // test 2: -n first
          key = ECKeyFac.inst().create();
          pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(EXIT, mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName, command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
          // test 3: known key
          assertEquals(ERROR, mockCli.callAndDoNotInitializeAvm(new String[] { "-n", currentTestNetName, command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
      }
  
      /**
       * Ensures that the { <i>-a import</i>, <i>a</i>, <i>--account import</i> } arguments work in
       * combination with the data directory and network options.
       */
      @Test
      @Parameters({"a|import", "a|i", "account|import", "account|i"})
      public void testImportPrivateKey_withDataDirAndNetwork(String command, String option) {
          // test 1: -d -n last
          ECKey key = ECKeyFac.inst().create();
          String pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(
                  ERROR,
                  mockCli.callAndDoNotInitializeAvm(new String[] {command, option, pKey, "-d", dataDirectory, "-n", currentTestNetName}, cfg));
          assertThat(Keystore.list().length).isEqualTo(0);
  
  
          // test 2: -d -n first
          key = ECKeyFac.inst().create();
          pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(
              EXIT,
              mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName, "-d", dataDirectory, command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
  
          // test 3: duplicate key
          assertEquals(
              ERROR,
              mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName, "-d", dataDirectory, command, option, pKey}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
  
          // test 4: ai middle
          key = ECKeyFac.inst().create();
          pKey = Hex.toHexString(key.getPrivKeyBytes());
  
          assertEquals(
                  ERROR,
                  mockCli.callAndDoNotInitializeAvm(new String[] {"-n", currentTestNetName, command, option, pKey, "-d", dataDirectory}, cfg));
          assertThat(Keystore.list().length).isEqualTo(1);
      }
  
      /**
       * Ensures that the { <i>-a import</i>, <i>a</i>, <i>--account import</i> } arguments fail when
       * a non-private key is supplied.
       */
      @Test
      @Parameters({"a|import", "a|i", "account|import", "account|i"})
      public void testImportNonPrivateKey(String command, String option) {
          String fakePKey = Hex.toHexString("random".getBytes());
  
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {command, option, fakePKey}, cfg)).isEqualTo(ERROR);
  
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {command, option, "hello"}, cfg)).isEqualTo(ERROR);
      }
  
      /** Parameters for testing {@link #testCheckArguments(String[], TaskPriority, Set<String>)}. */
      @SuppressWarnings("unused")
      private Object parametersForArgumentCheck() {
          List<Object> parameters = new ArrayList<>();
  
          String[] input;
          Set<String> skippedTasks;
  
          input = new String[] {"--info"};
          skippedTasks = new HashSet<>();
          parameters.add(new Object[] {input, TaskPriority.INFO, skippedTasks});
  
          input = new String[] {"-s create", "account","create"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("-s create");
          parameters.add(new Object[] {input, TaskPriority.ACCOUNT, skippedTasks});
  
          input = new String[] {"--info", "--config", "mainnet", "-s create"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--info");
          skippedTasks.add("-s create");
          parameters.add(new Object[] {input, TaskPriority.CONFIG, skippedTasks});
  
          input = new String[] {"--help", "--port", TEST_PORT};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--port");
          parameters.add(new Object[] {input, TaskPriority.HELP, skippedTasks});
  
          input = new String[] {"--help", "--db-compact"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--db-compact");
          parameters.add(new Object[] {input, TaskPriority.HELP, skippedTasks});
  
          input = new String[] {"--help", "--network", "mainnet", "--datadir", dataDirectory};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--network");
          skippedTasks.add("--datadir");
          parameters.add(new Object[] {input, TaskPriority.HELP, skippedTasks});
  
          input = new String[] {"--version", "--port", TEST_PORT, "--datadir", dataDirectory};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--port");
          skippedTasks.add("--datadir");
          parameters.add(new Object[] {input, TaskPriority.VERSION, skippedTasks});
  
          input = new String[] {"--version", "-v"};
          skippedTasks = new HashSet<>();
          parameters.add(new Object[] {input, TaskPriority.VERSION, skippedTasks});
  
          input = new String[] {"--version", "--port", TEST_PORT, "--db-compact"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--port");
          skippedTasks.add("--db-compact");
          parameters.add(new Object[] {input, TaskPriority.VERSION, skippedTasks});
  
          input = new String[] {"--prune-blocks", "--state", "FULL"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--state");
          parameters.add(new Object[] {input, TaskPriority.PRUNE_BLOCKS, skippedTasks});
  
          input = new String[] {"-h", "-v"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("-v");
          parameters.add(new Object[] {input, TaskPriority.HELP, skippedTasks});
  
          input = new String[] {"-h", "--version"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--version");
          parameters.add(new Object[] {input, TaskPriority.HELP, skippedTasks});
  
          input = new String[] {"-h", "-c"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--config");
          parameters.add(new Object[] {input, TaskPriority.HELP, skippedTasks});
  
          input = new String[] {"-i", "a", "c"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("account");
          parameters.add(new Object[] {input, TaskPriority.INFO, skippedTasks});
  
          ECKey key = ECKeyFac.inst().create();
          String pKey = Hex.toHexString(key.getPrivKeyBytes());
          input = new String[] {"-c","mainnet", "account","import", pKey};
          skippedTasks = new HashSet<>();
          skippedTasks.add("account");
          parameters.add(new Object[] {input, TaskPriority.CONFIG, skippedTasks});
  
          input = new String[] {"-s create", "-r", "100", "pb"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--revert");
          skippedTasks.add("--prune-blocks");
          parameters.add(new Object[] {input, TaskPriority.SSL, skippedTasks});
  
          input = new String[] {"-r", "100", "--state", "FULL", "--db-compact"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--state");
          skippedTasks.add("--db-compact");
          parameters.add(new Object[] {input, TaskPriority.REVERT, skippedTasks});
  
          input =
                  new String[] {
                      "--state", "FULL", "--db-compact"
                  };
          skippedTasks = new HashSet<>();
          skippedTasks.add("--db-compact");
          parameters.add(new Object[] {input, TaskPriority.PRUNE_STATE, skippedTasks});
  
          input = new String[] { "--db-compact","dev","print-state-size","4"};
          skippedTasks = new HashSet<>();
          skippedTasks.add("--db-compact");
          parameters.add(new Object[] {input, TaskPriority.DEV, skippedTasks});
          return parameters.toArray();
      }
  
      @Test
      @Parameters(method = "parametersForArgumentCheck")
      public void testCheckArguments(
              String[] input, TaskPriority expectedPriority, Set<String> expectedTasks) {
          Arguments options = new Arguments();
          CommandLine parser = new CommandLine(options);
          CommandLine.ParseResult result = parser.parseArgs(input);
  
          TaskPriority breakingTaskPriority = cli.getBreakingTaskPriority(options, result);
          assertEquals(expectedPriority, breakingTaskPriority);
          Set<String> skippedTasks = cli.getSkippedTasks(options, breakingTaskPriority, result);
          assertEquals(expectedTasks, skippedTasks);
      }
  
      /**
       * Ensures that the { <i>qt</i>, <i>--query-tx</i>} arguments fail when invalid transaction hash
       * is supplied. Separate 2 tests cause the db has load issue during the multiple parameters
       * input.
       */
      @Test
      @Parameters({"dev qt", "dev query-tx"})
      public void testQueryTransaction(String option) {
          String[] options = option.split("\\s");
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {options[0], options[1], "0xa0"}, cfg)).isEqualTo(ERROR);
      }
  
      /**
       * Ensures that the { <i>qb</i>, <i>--query-block</i>} arguments fail when no blocknumber is
       * supplied.
       */
      @Test
      @Parameters({"dev qb", "dev query-block"})
      public void testQueryBlock(String option) {
          String[] options = option.split("\\s");
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {options[0], options[1], ""}, cfg)).isEqualTo(ERROR);
      }
  
      /**
       * Ensures that the { <i>qa</i>, <i>--query-account</i>} arguments fail when invalid account
       * address is supplied.
       */
      @Test
      @Parameters({"dev query-account", "dev query-account"})
      public void testQueryAccount(String option) {
          String[] options = option.split("\\s");
          assertThat(mockCli.callAndDoNotInitializeAvm(new String[] {options[0], options[1], ""}, cfg)).isEqualTo(ERROR);
      }
  
  
  
      @Test
      @Parameters(method = "parametersForTestEdit")
      public void testEdit(String[] options){
          Arguments args = new Arguments();
          EditCli editCli = new EditCli();
  
          CommandLine parser = new CommandLine(args).addSubcommand(editCli);
          final CommandLine.ParseResult parseResult = parser.parseArgs(options).subcommand();
          editCli.checkOptions();
          assertThat(parseResult).isNotNull();
          assertThat(parseResult.commandSpec().userObject().getClass()).isEqualTo(EditCli.class);
  
          assertThat(mockCli.callAndDoNotInitializeAvm(options, cfg)).isEqualTo(RUN);
      }
  
      @Test(expected = RuntimeException.class)
      @Parameters(method = "parametersForTestFailedParse")
      public void testFailedParse(String[] options){
          Arguments args = new Arguments();
          EditCli editCli = new EditCli();
  
          CommandLine parser = new CommandLine(args).addSubcommand(editCli);
          parser.parseArgs(options).subcommand();
          editCli.checkOptions();
      }
  
      public Object[] parametersForTestEdit(){
          List<Object> parameters = new ArrayList<>();
  
          parameters.add("edit port=30000 state-storage=TOP vendor=h2 java=on rpc=off mining=on show-status=on compression=on log GEN=TRACE".split("\\s"));
          List<String[]> parameters2 = new ArrayList<>();
          String[] enableDisableArr = new String[]{"on", "off"};
          String[] settings = new String[]{"java", "rpc", "mining", "show-status","compression"};
          for (int i =0; i<enableDisableArr.length; i++){
              for (int j=0; j<settings.length;j++){
                  parameters2.add(new String[]{"edit",settings[j] + "=" + enableDisableArr[i]});
              }
          }
  
          String[] vendors = new String[]{"h2", "leveldb", "rocksdb"};
          for (int i = 0; i< vendors.length; i++){
              parameters.add(new String[]{"edit", "vendor="+vendors[i]});
          }
  
          for (int i=0; i< LogLevel.values().length; i++){
              StringBuilder builder = new StringBuilder();
              builder.append("edit log ");
              for(int j=0; j< LogEnum.values().length; j++){
                  builder.append(LogEnum.values()[j].name().toLowerCase())
                          .append("=")
                          .append(LogLevel.values()[i].name().toLowerCase()).append(" ");
  
              }
  
              parameters2.add(builder.toString().trim().split("\\s"));
          }
  
  
          for (int i = 0; i< CfgDb.PruneOption.values().length; i++){
              parameters.add(new String[]{"edit", "state-storage="+ CfgDb.PruneOption.values()[i].name().toLowerCase()});
          }
          parameters.addAll(parameters2);
          parameters.add("edit port=30000 state-storage=TOP vendor=h2 java=on rpc=off mining=on show-status=on compression=on log GEN=TRACE CONS=TRACE SYNC=TRACE API=TRACE VM=TRACE NET=TRACE DB=TRACE P2P=TRACE".split("\\s"));
          parameters.add("-n mainnet edit port=30000 state-storage=TOP vendor=h2 java=on rpc=off mining=on show-status=off compression=on log GEN=TRACE CONS=TRACE SYNC=TRACE API=TRACE VM=TRACE NET=TRACE DB=TRACE P2P=TRACE".split("\\s"));
  
          return parameters.toArray();
      }
  
      public Object[] parametersForTestFailedParse(){
          List<Object> parameters = new ArrayList<>();
  
          parameters.add("edit port=30000 state-storage=TOP -n mainnet vendor=h2 java=on rpc=off mining=on show-status=on compression=on log GEN=TRACE".split("\\s"));
          parameters.add("edit port=30000 state-storage=TOP vendor=h2 java=on rpc=off mining=on show-status=on compression=on log GEN=TRACE CONS=TRACE SYNC=TRACE API=TRACE VM=TRACE NET=TRACE DB=TRACE P2P=TRACE -n mainnet".split("\\s"));
          List<String[]> parameters2 = new ArrayList<>();
          String[] settings = new String[]{"java", "rpc", "mining", "show-status","compression", "port", "state-storage", "vendor", "log"};
          for (int j=0; j<settings.length;j++){
              parameters2.add(new String[]{"edit",settings[j]});
          }
  
          parameters.add("edit port=-100".split("\\n"));
          parameters.add("edit port=10000000000".split("\\n"));
          parameters.add("edit");
  
          parameters.addAll(parameters2);
          return parameters.toArray();
      }
  
      @Parameters(method = "parametersFortestCommandSpec")
      @Test
      public void testCommandSpec(String[] args, Object[] expectedClasses){
          CommandLine cli = new CommandLine(new Arguments()).addSubcommand("edit",new EditCli());
  
          CommandLine.ParseResult result = cli.parseArgs(args);
  
          for(Object expected: expectedClasses){
              CommandLine.Model.CommandSpec spec = Cli.findCommandSpec(result, (Class<?>) expected);
              assertThat(spec).isNotNull();
              assertThat(spec.userObject().getClass()).isEqualTo(expected);
          }
  
      }
  
      @Parameters(method = "parametersForTestCommandSpecFail")
      @Test(expected = RuntimeException.class)
      public void testCommandSpecFail(String[] args, Object[] expectedClasses){
          CommandLine cli = new CommandLine(new Arguments()).addSubcommand("edit",new EditCli());
  
          CommandLine.ParseResult result = cli.parseArgs(args);
  
          for(Object expected: expectedClasses){
              CommandLine.Model.CommandSpec spec = Cli.findCommandSpec(result, (Class<?>) expected);
              Objects.requireNonNull(spec);
          }
  
      }
  
      public Object parametersForTestCommandSpecFail(){
          Object[] arr1 = new Object[]{"editt port=1000 devv --help".split("\\s"), List.of(EditCli.class, DevCLI.class).toArray()};
          Object[] arr2 = new Object[]{"DEV --help".split("\\s"), List.of(DevCLI.class).toArray()};
          Object[] arr3 = new Object[]{"EDIT port=1000".split("\\s"), List.of(EditCli.class).toArray()};
          Object[] arr4 = new Object[]{"dev --help".split("\\s"), List.of(DevCLI.class, EditCli.class).toArray()};
          Object[] arr5 = new Object[]{"edit port=1000".split("\\s"), List.of( EditCli.class, DevCLI.class).toArray()};
          Object[] arr6 = new Object[]{"".split("\\s"), List.of(DevCLI.class, EditCli.class).toArray()};
          Object[] arr7 = new Object[]{"".split("\\s"), List.of(EditCli.class, DevCLI.class).toArray()};
          Object[] arr8 = new Object[]{"dev --help edit port=1000".split("\\s"), List.of(EditCli.class, DevCLI.class).toArray()};
  
          return List.of(arr1, arr2, arr3, arr4, arr5, arr6, arr7, arr8).toArray();
      }
  
      public Object parametersFortestCommandSpec(){
          Object[] arr1 = new Object[]{"edit port=1000 dev --help".split("\\s"), List.of(EditCli.class, DevCLI.class).toArray()};
          Object[] arr2 = new Object[]{"dev --help".split("\\s"), List.of(DevCLI.class).toArray()};
          Object[] arr3 = new Object[]{"edit port=1000".split("\\s"), List.of(EditCli.class).toArray()};
  
          return List.of(arr1, arr2, arr3).toArray();
      }
  
      @Parameters(method = "parametersForTestParseDev")
      @Test
      public void  testParseDev(String[] command){
          CommandLine commandLine  = new CommandLine(new Arguments()).addSubcommand(EditCli.class);
          final CommandLine.ParseResult result = commandLine.parseArgs(command);
  
          CommandLine.Model.CommandSpec spec = Cli.findCommandSpec(result, DevCLI.class);
  
          assertThat(spec).isNotNull();
          assertThat(spec.userObject()).isNotNull();
          assertThat(((DevCLI) spec.userObject()).getArgs()).isNotNull();
          ((DevCLI) spec.userObject()).getArgs().checkOptions();
      }
  
  
  
      public Object parametersForTestParseDev(){
          String[] options = new String[]{"-h", "--help", "write-blocks=1","wb=1",  "cs=1","print-state-size=1", "print-state=1","ps=1",
                  "print-for-test 10 true str1 str2", "pf 10 true str1 str2","pb=4","print-block=4", "pt=str1", "print-tx=str2", "pa=str1", "print-account=str2", "xs=10", "stop-at=10"};
  
          String[] commands = new String[]{"edit port 100 dev", "dev", "d"};
          List<String[]> strings = new ArrayList<>();
          for (String option: options){
              for (String command: commands){
                  String tmp = command + " "+ option;
                  strings.add(tmp.split("\\s"));
              }
          }
          return strings.toArray();
      }
  
      /*
      Ensure that the dev command only accepts one param within the mutually exclusive group of params
       */
      @Parameters(method = "parametersForTestParseDevFail")
      @Test(expected = RuntimeException.class)
      public void testParseDevFail(String[] arr){
          CommandLine commandLine  = new CommandLine(new Arguments()).addSubcommand(EditCli.class);
          commandLine.parseArgs(arr);
      }
  
  
      public Object[] parametersForTestParseDevFail(){
          String[] options = new String[]{"-h", "--help", "write-blocks=1","wb=1",  "cs=1","print-state-size=1", "print-state=1","ps=1",
              "print-for-test 10 true str1 str2", "pt 10 true str1 str2","pb=4","print-block=4", "pt=str1", "print-tx=str2", "pa=str1", "print-account=str2", "xs=10", "stop-at=10"};
  
          String[] commands = new String[]{"edit port 100 dev", "dev"};
          List<String[]> strings = new ArrayList<>();
          for (String option: options){
              for (String command: commands) {
                  for (String option1: options) {
                      String tmp = command + " " + option + " " + option1;
                      strings.add(tmp.split("\\s"));
  
                  }
              }
          }
          strings.add(new String[]{"dev"});
          strings.add("edit port 100 dev".split("\\s"));
  
          return strings.toArray();
      }
  
      @Parameters(method = "parametersFortestAccountCliParseAndRun")
      @Test
      public void testAccountCliParseAndRun(String[] options, ReturnType expected ){
  
          CommandLine commandLine = new CommandLine(Arguments.class).addSubcommand(EditCli.class);
          ParseResult result;
          try{
              result = commandLine.parseArgs(options);
          }catch (Exception e){
              if (expected.equals(ERROR)) {
                  return;
              }
              else {
                  throw e;
              }
          }
          CommandSpec commandSpec = Cli.findCommandSpec(result, AccountCli.class);
  
          //noinspection ConstantConditions
          AccountCli spiedAccountCLI = spy((AccountCli) commandSpec.userObject());
          doReturn(true).when(spiedAccountCLI).exportPrivateKey(anyString(),any());
          doReturn(true).when(spiedAccountCLI).listAccounts();
          doReturn(true).when(spiedAccountCLI).createAccount(any());
          doReturn(true).when(spiedAccountCLI).importPrivateKey(anyString(), any());
          doCallRealMethod().when(spiedAccountCLI).runCommand(any());
          assertThat(spiedAccountCLI.runCommand(mockpr)).isEqualTo(expected);
          if (spiedAccountCLI.getList()) {
              verify(spiedAccountCLI, times(1)).listAccounts();
          } else {
              verify(spiedAccountCLI, times(0)).listAccounts();
          }
  
          if (!spiedAccountCLI.getGroup().getImportAcc().isEmpty()) {
              verify(spiedAccountCLI, times(1)).importPrivateKey(anyString(), any());
          } else {
              verify(spiedAccountCLI, times(0)).importPrivateKey(anyString(),any());
          }
  
          if (!spiedAccountCLI.getGroup().getExport().isEmpty()) {
              verify(spiedAccountCLI, times(1)).exportPrivateKey(anyString(),any());
          } else {
              verify(spiedAccountCLI, times(0)).exportPrivateKey(anyString(), any());
          }
  
          if (spiedAccountCLI.getGroup().getCreate()) {
              verify(spiedAccountCLI, times(1)).createAccount(any());
          } else {
              verify(spiedAccountCLI, times(0)).createAccount(any());
          }
          Mockito.clearInvocations(spiedAccountCLI);
      }
  
      public Object parametersFortestAccountCliParseAndRun(){
          List<Object[]> params = new ArrayList<>();
  
          List<String> exportList = List.of("","export validstring","e validstring");
          List<String> importList = List.of("", "import validString", "i validString");
          List<String> create = List.of("", "create", "c");
          List<String> list = List.of("", "list", "l");
  
          for (String e: exportList){
              for (String i: importList){
                  for (String c: create){
                      for (String l : list) {
                          String options = String.format("%s %s %s %s", e, i, c, l);
  
                          ReturnType expected;
                          if(options.trim().isEmpty()){
                              expected = ERROR;
                          } else if (((e.isEmpty() && i.isEmpty())
                                  || (e.isEmpty() && c.isEmpty())
                                  || (i.isEmpty() && c.isEmpty()))) {
                              expected = EXIT;
                          } else {
                              expected = ERROR;
                          }
                          String[] commands = new String[] {"a", "-a", "account", "--account"};
                          for (String command : commands) {
                              params.add(
                                      new Object[] {
                                          Arrays.stream((command + " " + options).trim().split("\\s"))
                                                  .filter(s -> !s.isEmpty())
                                                  .toArray(String[]::new),
                                          expected
                                      });
                          }
                      }
                  }
              }
          }
          return params.toArray();
      }
  
      // Methods below taken from FileUtils class
      private static boolean copyRecursively(File src, File target) {
          if (src.isDirectory()) {
              return copyDirectoryContents(src, target);
          } else {
              try {
                  Files.copy(src, target);
                  return true;
              } catch (IOException e) {
                  return false;
              }
          }
      }
  
      private static boolean copyDirectoryContents(File src, File target) {
          Preconditions.checkArgument(src.isDirectory(), "Source dir is not a directory: %s", src);
  
          // Don't delete symbolic link directories
          if (isSymbolicLink(src)) {
              return false;
          }
  
          target.mkdirs();
          Preconditions.checkArgument(target.isDirectory(), "Target dir is not a directory: %s", src);
  
          boolean success = true;
          for (File file : listFiles(src)) {
              success = copyRecursively(file, new File(target, file.getName())) && success;
          }
          return success;
      }
  
      private static boolean isSymbolicLink(File file) {
          try {
              File canonicalFile = file.getCanonicalFile();
              File absoluteFile = file.getAbsoluteFile();
              File parentFile = file.getParentFile();
              // a symbolic link has a different name between the canonical and absolute path
              return !canonicalFile.getName().equals(absoluteFile.getName())
                      ||
                      // or the canonical parent path is not the same as the file's parent path,
                      // provided the file has a parent path
                      parentFile != null
                              && !parentFile.getCanonicalPath().equals(canonicalFile.getParent());
          } catch (IOException e) {
              // error on the side of caution
              return true;
          }
      }
  
      private static ImmutableList<File> listFiles(File dir) {
          File[] files = dir.listFiles();
          if (files == null) {
              return ImmutableList.of();
          }
          return ImmutableList.copyOf(files);
      }
  
      private static boolean deleteRecursively(File file) {
          Path path = file.toPath();
          try {
              java.nio.file.Files.walkFileTree(
                      path,
                      new SimpleFileVisitor<>() {
                          @Override
                          public FileVisitResult visitFile(
                                  final Path file, final BasicFileAttributes attrs)
                                  throws IOException {
                              java.nio.file.Files.delete(file);
                              return FileVisitResult.CONTINUE;
                          }
  
                          @Override
                          public FileVisitResult visitFileFailed(
                                  final Path file, final IOException e) {
                              return handleException(e);
                          }
  
                          private FileVisitResult handleException(final IOException e) {
                              // e.printStackTrace();
                              return FileVisitResult.TERMINATE;
                          }
  
                          @Override
                          public FileVisitResult postVisitDirectory(
                                  final Path dir, final IOException e) throws IOException {
                              if (e != null) {
                                  return handleException(e);
                              }
                              java.nio.file.Files.delete(dir);
                              return FileVisitResult.CONTINUE;
                          }
                      });
          } catch (IOException e) {
              e.printStackTrace();
              return false;
          }
  
          return true;
      }
  }
  