You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by dm...@apache.org on 2015/10/22 15:01:33 UTC
[17/42] ignite git commit: ignite-1559: UriDeploymentHttpScanner
tests and javadoc
ignite-1559: UriDeploymentHttpScanner tests and javadoc
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/02b59e43
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/02b59e43
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/02b59e43
Branch: refs/heads/ignite-1272
Commit: 02b59e433bce7a4c3eece7a80e7a053ae0d69373
Parents: a228c24
Author: Artem SHutak <as...@gridgain.com>
Authored: Thu Oct 15 17:18:15 2015 -0700
Committer: Valentin Kulichenko <va...@gmail.com>
Committed: Thu Oct 15 17:18:15 2015 -0700
----------------------------------------------------------------------
.../ignite/spi/deployment/DeploymentSpi.java | 8 +-
modules/core/src/test/config/tests.properties | 3 +
modules/extdata/uri/pom.xml | 11 +-
.../spi/deployment/uri/UriDeploymentSpi.java | 93 ++++++++-----
.../scanners/http/UriDeploymentHttpScanner.java | 10 +-
.../http/GridHttpDeploymentSelfTest.java | 132 +++++++++++++++++--
6 files changed, 204 insertions(+), 53 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ignite/blob/02b59e43/modules/core/src/main/java/org/apache/ignite/spi/deployment/DeploymentSpi.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/spi/deployment/DeploymentSpi.java b/modules/core/src/main/java/org/apache/ignite/spi/deployment/DeploymentSpi.java
index 7a1f709..af09e48 100644
--- a/modules/core/src/main/java/org/apache/ignite/spi/deployment/DeploymentSpi.java
+++ b/modules/core/src/main/java/org/apache/ignite/spi/deployment/DeploymentSpi.java
@@ -46,13 +46,13 @@ import org.jetbrains.annotations.Nullable;
* local deployment.
* </strong>
* <p>
- * Ignite provides the following {@code GridDeploymentSpi} implementations:
+ * Ignite provides following {@code GridDeploymentSpi} implementations out of the box:
* <ul>
* <li>{@link org.apache.ignite.spi.deployment.local.LocalDeploymentSpi}</li>
* <li>{@ignitelink org.apache.ignite.spi.deployment.uri.UriDeploymentSpi}</li>
* </ul>
- * <b>NOTE:</b> this SPI (i.e. methods in this interface) should never be used directly. SPIs provide
- * internal view on the subsystem and is used internally by Ignite kernal. In rare use cases when
+ * <b>NOTE:</b> SPI methods should never be used directly. SPIs provide
+ * internal view on the subsystem and is used internally by Ignite. In rare use cases when
* access to a specific implementation of this SPI is required - an instance of this SPI can be obtained
* via {@link org.apache.ignite.Ignite#configuration()} method to check its configuration properties or call other non-SPI
* methods. Note again that calling methods from this interface on the obtained instance can lead
@@ -104,4 +104,4 @@ public interface DeploymentSpi extends IgniteSpi {
* @param lsnr Listener for deployment events. {@code null} to unset the listener.
*/
public void setListener(@Nullable DeploymentListener lsnr);
-}
\ No newline at end of file
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/02b59e43/modules/core/src/test/config/tests.properties
----------------------------------------------------------------------
diff --git a/modules/core/src/test/config/tests.properties b/modules/core/src/test/config/tests.properties
index ce6d4d8..49e616e 100644
--- a/modules/core/src/test/config/tests.properties
+++ b/modules/core/src/test/config/tests.properties
@@ -67,7 +67,10 @@ ant.gar.srcdir=@{IGNITE_HOME}/modules/extdata/uri/target/classes/
# GAR paths to use in URI deployment SPI tests
ant.urideployment.gar.uri=file://freq=5000@localhost/EXTDATA/uri/target/deploy
ant.urideployment.gar.file=modules/extdata/uri/target/deploy/uri.gar
+ant.urideployment.gar.libs-file=modules/extdata/uri/target/deploy/uri-libs.gar
+ant.urideployment.gar.classes-file=modules/extdata/uri/target/deploy/uri-classes.gar
ant.urideployment.gar.path=modules/extdata/uri/target/deploy/
+ant.urideployment.gar.path.tmp=modules/extdata/uri/target/deploy_tmp/
# Classpath directory for GridP2PUserVersionChangeSelfTest
ant.userversion.class.dir=@{IGNITE_HOME}/modules/tests/java/
http://git-wip-us.apache.org/repos/asf/ignite/blob/02b59e43/modules/extdata/uri/pom.xml
----------------------------------------------------------------------
diff --git a/modules/extdata/uri/pom.xml b/modules/extdata/uri/pom.xml
index d9a9297..d5e6349 100644
--- a/modules/extdata/uri/pom.xml
+++ b/modules/extdata/uri/pom.xml
@@ -144,14 +144,23 @@
</fileset>
</copy>
- <copy file="${settings.localRepository}/com/sun/mail/javax.mail/1.5.2/javax.mail-1.5.2.jar" todir="${basedir}/target/classes/lib" />
+ <copy file="${settings.localRepository}/com/sun/mail/javax.mail/1.5.2/javax.mail-1.5.2.jar" todir="${basedir}/target/libs" />
<zip destfile="${basedir}/target/classes/lib/depend.jar" encoding="UTF-8">
<zipfileset dir="modules/uri-dependency/target/classes" />
</zip>
+ <copy file="${basedir}/target/classes/lib/depend.jar" todir="${basedir}/target/libs" />
+
+ <mkdir dir="${basedir}/target/deploy_tmp/"/>
+
<taskdef name="gar" classname="org.apache.ignite.util.antgar.IgniteDeploymentGarAntTask" />
+ <gar destfile="${basedir}/target/deploy/uri-classes.gar" basedir="${basedir}/target/classes" />
+ <gar destfile="${basedir}/target/deploy/uri-libs.gar" basedir="${basedir}/target/libs" />
+
+ <copy file="${settings.localRepository}/com/sun/mail/javax.mail/1.5.2/javax.mail-1.5.2.jar" todir="${basedir}/target/classes/lib" />
+
<gar destfile="${basedir}/target/deploy/uri.gar" basedir="${basedir}/target/classes" />
<!--
http://git-wip-us.apache.org/repos/asf/ignite/blob/02b59e43/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/UriDeploymentSpi.java
----------------------------------------------------------------------
diff --git a/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/UriDeploymentSpi.java b/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/UriDeploymentSpi.java
index c48398d..5f65731 100644
--- a/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/UriDeploymentSpi.java
+++ b/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/UriDeploymentSpi.java
@@ -80,7 +80,7 @@ import org.jetbrains.annotations.Nullable;
* <p>
* SPI tracks all changes of every given URI. This means that if any file is
* changed or deleted, SPI will re-deploy or delete corresponding tasks.
- * Note that the very first apply to {@link #findResource(String)} findClassLoader(String)}
+ * Note that the very first apply to {@link #findResource(String)}
* is blocked until SPI finishes scanning all URI's at least once.
* <p>
* There are several deployable unit types supported:
@@ -145,11 +145,33 @@ import org.jetbrains.annotations.Nullable;
* URI 'path' field will be automatically encoded. By default this flag is
* set to {@code true}.
* <p>
+ * <h1 class="header">Code Example</h1>
+ * The following example demonstrates how the deployment SPI can be used. It expects that you have a GAR file
+ * in 'home/username/ignite/work/my_deployment/file' folder which contains 'myproject.HelloWorldTask' class.
+ * <pre name="code" class="java">
+ * IgniteConfiguration cfg = new IgniteConfiguration();
+ *
+ * DeploymentSpi deploymentSpi = new UriDeploymentSpi();
+ *
+ * deploymentSpi.setUriList(Arrays.asList("file:///home/username/ignite/work/my_deployment/file"));
+ *
+ * cfg.setDeploymentSpi(deploymentSpi);
+ *
+ * try (Ignite ignite = Ignition.start(cfg)) {
+ * ignite.compute().execute("myproject.HelloWorldTask", "my args");
+ * }
+ * </pre>
* <h1 class="header">Configuration</h1>
* {@code UriDeploymentSpi} has the following optional configuration
* parameters (there are no mandatory parameters):
* <ul>
* <li>
+ * Array of {@link UriDeploymentScanner}-s which will be used to deploy resources
+ * (see {@link #setScanners(UriDeploymentScanner...)}). If not specified, preconfigured {@link UriDeploymentFileScanner}
+ * and {@link UriDeploymentHttpScanner} are used. You can implement your own scanner
+ * by implementing {@link UriDeploymentScanner} interface.
+ * </li>
+ * <li>
* Temporary directory path where scanned GAR files and directories are
* copied to (see {@link #setTemporaryDirectoryPath(String) setTemporaryDirectoryPath(String)}).
* </li>
@@ -163,25 +185,28 @@ import org.jetbrains.annotations.Nullable;
* </li>
* </ul>
* <h1 class="header">Protocols</h1>
- * Following protocols are supported in SPI:
+ * Following protocols are supported by this SPI out of the box:
* <ul>
* <li><a href="#file">file://</a> - File protocol</li>
- * <li><a href="#classes">classes://</a> - Custom File protocol.</li>
* <li><a href="#http">http://</a> - HTTP protocol</li>
* <li><a href="#http">https://</a> - Secure HTTP protocol</li>
* </ul>
+ * <strong>Custom Protocols.</strong>
+ * <p>
+ * You can add support for additional protocols if needed. To do this implement UriDeploymentScanner interface and
+ * plug your implementation into the SPI via {@link #setScanners(UriDeploymentScanner...)} method.
+ * <p>
* In addition to SPI configuration parameters, all necessary configuration
* parameters for selected URI should be defined in URI. Different protocols
* have different configuration parameters described below. Parameters are
* separated by '{@code ;}' character.
* <p>
- * <a name="file"></a>
* <h1 class="header">File</h1>
* For this protocol SPI will scan folder specified by URI on file system and
* download any GAR files or directories that end with .gar from source
* directory defined in URI. For file system URI must have scheme equal to {@code file}.
* <p>
- * Following parameters are supported for FILE protocol:
+ * Following parameters are supported:
* <table class="doctable">
* <tr>
* <th>Parameter</th>
@@ -189,24 +214,29 @@ import org.jetbrains.annotations.Nullable;
* <th>Optional</th>
* <th>Default</th>
* </tr>
+ * <tr>
+ * <td>freq</td>
+ * <td>Scanning frequency in milliseconds.</td>
+ * <td>Yes</td>
+ * <td>{@code 5000} ms specified in {@link UriDeploymentFileScanner#DFLT_SCAN_FREQ}.</td>
+ * </tr>
* </table>
* <h2 class="header">File URI Example</h2>
* The following example will scan {@code 'c:/Program files/ignite/deployment'}
- * folder on local box every {@code '5000'} milliseconds. Note that since path
+ * folder on local box every {@code '1000'} milliseconds. Note that since path
* has spaces, {@link #setEncodeUri(boolean) setEncodeUri(boolean)} parameter must
* be set to {@code true} (which is default behavior).
* <blockquote class="snippet">
- * {@code file://freq=5000@localhost/c:/Program files/ignite/deployment}
+ * {@code file://freq=2000@localhost/c:/Program files/ignite/deployment}
* </blockquote>
* <a name="classes"></a>
- * <h1 class="header">Classes</h1>
- * For this protocol SPI will scan folder specified by URI on file system
- * looking for compiled classes that implement {@link org.apache.ignite.compute.ComputeTask} interface.
- * This protocol comes very handy during development, as it allows developer
- * to specify IDE compilation output folder as URI and all task classes
- * in that folder will be deployed automatically.
+ * <h2 class="header">HTTP/HTTPS</h2>
+ * URI deployment scanner tries to read DOM of the html it points to and parses out href attributes of all <a> tags
+ * - this becomes the collection of URLs to GAR files that should be deployed. It's important that HTTP scanner
+ * uses {@code URLConnection.getLastModified()} method to check if there were any changes since last iteration
+ * for each GAR-file before redeploying.
* <p>
- * Following parameters are supported for CLASSES protocol:
+ * Following parameters are supported:
* <table class="doctable">
* <tr>
* <th>Parameter</th>
@@ -214,20 +244,17 @@ import org.jetbrains.annotations.Nullable;
* <th>Optional</th>
* <th>Default</th>
* </tr>
+ * <tr>
+ * <td>freq</td>
+ * <td>Scanning frequency in milliseconds.</td>
+ * <td>Yes</td>
+ * <td>{@code 300000} ms specified in {@link UriDeploymentHttpScanner#DFLT_SCAN_FREQ}.</td>
+ * </tr>
* </table>
- * <h2 class="header">Classes URI Example</h2>
- * The following example will scan {@code 'c:/Program files/ignite/deployment'}
- * folder on local box every {@code '5000'} milliseconds. Note that since path
- * has spaces, {@link #setEncodeUri(boolean) setEncodeUri(boolean)} parameter must
- * be set to {@code true} (which is default behavior).
- * <blockquote class="snippet">
- * {@code classes://freq=5000@localhost/c:/Program files/ignite/deployment}
- * </blockquote>
- * <a name="http"></a>
* <h2 class="header">HTTP URI Example</h2>
- * The following example will scan {@code 'ignite/deployment'} folder with
- * on site {@code 'www.mysite.com'} using authentication
- * {@code 'username:password'} every {@code '10000'} milliseconds.
+ * The following example will download the page `www.mysite.com/ignite/deployment`, parse it and download and deploy
+ * all GAR files specified by href attributes of <a> elements on the page using authentication
+ * {@code 'username:password'} every '10000' milliseconds (only new/updated GAR-s).
* <blockquote class="snippet">
* {@code http://username:password;freq=10000@www.mysite.com:110/ignite/deployment}
* </blockquote>
@@ -238,14 +265,9 @@ import org.jetbrains.annotations.Nullable;
*
* IgniteConfiguration cfg = new IgniteConfiguration();
*
- * List<String> uris = new ArrayList<String>(5);
- *
- * uris.add("http://www.site.com/tasks");
- * uris.add("file://freq=20000@localhost/c:/Program files/gg-deployment");
- * uris.add("classes:///c:/Java_Projects/myproject/out");
- *
* // Set URIs.
- * deploySpi.setUriList(uris);
+ * deploySpi.setUriList(Arrays.asList("http://www.site.com/tasks",
+ * "file://freq=20000@localhost/c:/Program files/gg-deployment"));
*
* // Override temporary directory path.
* deploySpi.setTemporaryDirectoryPath("c:/tmp/grid");
@@ -254,7 +276,7 @@ import org.jetbrains.annotations.Nullable;
* cfg.setDeploymentSpi(deploySpi);
*
* // Start grid.
- * G.start(cfg);
+ * Ignition.start(cfg);
* </pre>
* <p>
* <h2 class="header">Spring Example</h2>
@@ -269,7 +291,6 @@ import org.jetbrains.annotations.Nullable;
* <list>
* <value>http://www.site.com/tasks</value>
* <value>file://freq=20000@localhost/c:/Program files/gg-deployment</value>
- * <value>classes:///c:/Java_Projects/myproject/out</value>
* </list>
* </property>
* </bean>
@@ -1325,4 +1346,4 @@ public class UriDeploymentSpi extends IgniteSpiAdapter implements DeploymentSpi,
@Override public String toString() {
return S.toString(UriDeploymentSpi.class, this);
}
-}
\ No newline at end of file
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/02b59e43/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/scanners/http/UriDeploymentHttpScanner.java
----------------------------------------------------------------------
diff --git a/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/scanners/http/UriDeploymentHttpScanner.java b/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/scanners/http/UriDeploymentHttpScanner.java
index ef29752..48bfd7f 100644
--- a/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/scanners/http/UriDeploymentHttpScanner.java
+++ b/modules/urideploy/src/main/java/org/apache/ignite/spi/deployment/uri/scanners/http/UriDeploymentHttpScanner.java
@@ -60,11 +60,15 @@ import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;
/**
- * URI deployment HTTP scanner.
+ * HTTP-based URI deployment scanner.
+ * <p>
+ * This scanner reads DOM of the HTML available via {@link UriDeploymentScannerContext#getUri()}
+ * and parses out href attributes of all {@code <a>} tags -
+ * they become the collection of URLs to GAR files that should be deployed.
*/
public class UriDeploymentHttpScanner implements UriDeploymentScanner {
/** Default scan frequency. */
- private static final int DFLT_SCAN_FREQ = 300000;
+ public static final int DFLT_SCAN_FREQ = 300000;
/** Secure socket protocol to use. */
private static final String PROTOCOL = "TLS";
@@ -501,4 +505,4 @@ public class UriDeploymentHttpScanner implements UriDeploymentScanner {
return true;
}
}
-}
\ No newline at end of file
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/02b59e43/modules/urideploy/src/test/java/org/apache/ignite/spi/deployment/uri/scanners/http/GridHttpDeploymentSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/urideploy/src/test/java/org/apache/ignite/spi/deployment/uri/scanners/http/GridHttpDeploymentSelfTest.java b/modules/urideploy/src/test/java/org/apache/ignite/spi/deployment/uri/scanners/http/GridHttpDeploymentSelfTest.java
index 216d0ab..c0044c3 100644
--- a/modules/urideploy/src/test/java/org/apache/ignite/spi/deployment/uri/scanners/http/GridHttpDeploymentSelfTest.java
+++ b/modules/urideploy/src/test/java/org/apache/ignite/spi/deployment/uri/scanners/http/GridHttpDeploymentSelfTest.java
@@ -17,6 +17,8 @@
package org.apache.ignite.spi.deployment.uri.scanners.http;
+import java.io.File;
+import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
@@ -38,8 +40,35 @@ import static org.eclipse.jetty.http.HttpHeader.LAST_MODIFIED;
*/
@GridSpiTest(spi = UriDeploymentSpi.class, group = "Deployment SPI")
public class GridHttpDeploymentSelfTest extends GridUriDeploymentAbstractSelfTest {
+ /** Frequency */
+ private static final int FREQ = 5000;
+
+ /** */
+ public static final String LIBS_GAR = "libs-file.gar";
+
+ /** */
+ public static final String CLASSES_GAR = "classes-file.gar";
+
+ /** */
+ public static final String ALL_GAR = "file.gar";
+
+ /** Gar-file which contains libs. */
+ public static final String LIBS_GAR_FILE_PATH = U.resolveIgnitePath(
+ GridTestProperties.getProperty("ant.urideployment.gar.libs-file")).getPath();
+
+ /** Gar-file which contains classes (cannot be used without libs). */
+ public static final String CLASSES_GAR_FILE_PATH = U.resolveIgnitePath(
+ GridTestProperties.getProperty("ant.urideployment.gar.classes-file")).getPath();
+
+ /** Gar-file which caontains both libs and classes. */
+ public static final String ALL_GAR_FILE_PATH = U.resolveIgnitePath(
+ GridTestProperties.getProperty("ant.urideployment.gar.file")).getPath();
+
/** Jetty. */
- private Server srv;
+ private static Server srv;
+
+ /** Resource base. */
+ private static String rsrcBase;
/** {@inheritDoc} */
@Override protected void beforeSpiStarted() throws Exception {
@@ -60,8 +89,12 @@ public class GridHttpDeploymentSelfTest extends GridUriDeploymentAbstractSelfTes
};
hnd.setDirectoriesListed(true);
- hnd.setResourceBase(
- U.resolveIgnitePath(GridTestProperties.getProperty("ant.urideployment.gar.path")).getPath());
+
+ File resourseBaseDir = U.resolveIgnitePath(GridTestProperties.getProperty("ant.urideployment.gar.path.tmp"));
+
+ rsrcBase = resourseBaseDir.getPath();
+
+ hnd.setResourceBase(rsrcBase);
srv.setHandler(hnd);
@@ -82,11 +115,63 @@ public class GridHttpDeploymentSelfTest extends GridUriDeploymentAbstractSelfTes
}
/**
- * @throws Exception if failed.
+ * @throws Exception If failed.
*/
- public void testDeployment() throws Exception {
- checkTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
- checkTask("GridUriDeploymentTestWithNameTask3");
+ public void testDeployUndeploy2Files() throws Exception {
+ checkNoTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
+
+ try {
+ copyToResourceBase(LIBS_GAR_FILE_PATH, LIBS_GAR);
+
+ copyToResourceBase(CLASSES_GAR_FILE_PATH, CLASSES_GAR);
+
+ Thread.sleep(FREQ + 3000);
+
+ checkTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ deleteFromResourceBase(LIBS_GAR);
+ deleteFromResourceBase(CLASSES_GAR);
+
+ Thread.sleep(FREQ + 3000);
+
+ checkNoTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
+ }
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ public void testSameContantFiles() throws Exception {
+ checkNoTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
+
+ try {
+ copyToResourceBase(ALL_GAR_FILE_PATH, ALL_GAR);
+
+ Thread.sleep(FREQ + 3000);
+
+ checkTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
+
+ copyToResourceBase(ALL_GAR_FILE_PATH, "file-copy.gar");
+
+ Thread.sleep(FREQ + 3000);
+
+ checkTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
+ }
+ catch (Throwable e) {
+ e.printStackTrace();
+ }
+ finally {
+ deleteFromResourceBase(ALL_GAR);
+ deleteFromResourceBase("file-copy.gar");
+
+ Thread.sleep(FREQ + 3000);
+
+ checkNoTask("org.apache.ignite.spi.deployment.uri.tasks.GridUriDeploymentTestTask3");
+ }
}
/**
@@ -94,6 +179,35 @@ public class GridHttpDeploymentSelfTest extends GridUriDeploymentAbstractSelfTes
*/
@GridSpiTestConfig
public List<String> getUriList() {
- return Collections.singletonList("http://freq=5000@localhost:8080/");
+ return Collections.singletonList("http://freq="+FREQ+"@localhost:8080/");
+ }
+
+ /**
+ * @param fileName File name.
+ */
+ private void deleteFromResourceBase(String fileName) {
+ File file = new File(rsrcBase + '/' + fileName);
+
+ if (!file.delete())
+ U.warn(log, "Could not delete file: " + file);
+ }
+
+ /**
+ * @param path Path to the file which should be copied.
+ * @param newFileName New file name.
+ * @throws IOException If exception.
+ */
+ private void copyToResourceBase(String path, String newFileName) throws IOException {
+ File file = new File(path);
+
+ assert file.exists() : "Test file not found [path=" + path + ']';
+
+ File newFile = new File(rsrcBase + '/' + newFileName);
+
+ assert !newFile.exists();
+
+ U.copy(file, newFile, false);
+
+ assert newFile.exists();
}
-}
\ No newline at end of file
+}