Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRILL-6272: Refactor dynamic UDFs and function initializer tests to g… #1225

Closed
wants to merge 2 commits into from

Conversation

arina-ielchiieva
Copy link
Member

…enerate needed binary and source jars at runtime

Details in DRILL-6272.

@arina-ielchiieva
Copy link
Member Author

@vrozov please review.

Copy link
Member

@vrozov vrozov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be easier to use (embedded) maven to create necessary jars?

URL[] urls = {jars.resolve(binaryName).toUri().toURL(), jars.resolve(sourceName).toUri().toURL()};
String binaryName = "DrillUDF-1.0";
URL template = ClassLoader.getSystemClassLoader().getResource("udf/dynamic/CustomLowerFunctionTemplate");
assert template != null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use junit assert in unit tests.

@@ -0,0 +1,45 @@
package org.apache.drill.udf.dynamic;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apache license

out.end = outputValue.getBytes().length;
buffer.setBytes(0, outputValue.getBytes());
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LF

@arina-ielchiieva arina-ielchiieva force-pushed the DRILL-6272 branch 2 times, most recently from d17439f to ac2c97d Compare April 20, 2018 12:05
@arina-ielchiieva
Copy link
Member Author

@vrozov addressed code review comments.
Regarding embedded maven, I have considered this option but using native Java to generate jars is more convenient. First of all, for the tests we need different jars, with and without resource file, classes. Doing it in Java allows to store less config and template files. Also we do not depend from any build tool. Plus it much easier to debug and run test using IDE or maven. I suggest we leave the current approach.

@vrozov
Copy link
Member

vrozov commented Apr 20, 2018

@arina-ielchiieva I don't see why using maven embedder is a less preferable option. Using maven it is possible to create source jars using standard maven plugin. IMO, it will be easier to modify test UDF project if necessary and it is still possible to debug and run tests using IDE or maven. There will be no dependency on a build tool, it is a dependency on an external jar similar to any other external jar dependency.

@arina-ielchiieva
Copy link
Member Author

@vrozov I'll re-work PR with maven embedder, thanks for the idea. I'll ping you when changes are done.

@arina-ielchiieva
Copy link
Member Author

@vrozov re-implemented using maven embedder. Had to upgrade jmokcit lib to the latest version since it caused NPE with maven embedder (NPE from jmockit even if no mocks are used, fixed in newer version). Please review.

pom.xml Outdated
@@ -798,7 +798,7 @@
<!-- JMockit needs to be on class path before JUnit. -->
<groupId>com.googlecode.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.3</version>
<version>1.7</version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be done as a precursor PR? 1.7 version is quite old too. Can it be upgraded to the latest (org.jmockit:jmockit:1.39)?

<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>3.3.9</version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using the latest release available.

@arina-ielchiieva
Copy link
Member Author

@vrozov now PR contains two commits:

  1. jmockit and mockito upgrade (DRILL-6363);
  2. maven-embedder usage for unit tests (used latest version as you suggested) (DRILL-6272).
    Please review.

@@ -177,7 +177,7 @@ public void run() {
}
}

//@Test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason the test was disabled before?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have accidentally disabled in my previous PR. Did not want to create separate Jira for one line fix so included it here.

@@ -125,7 +125,7 @@ public void testHasPathThrowsDrillRuntimeException() {

Mockito
.when(client.getCache().getCurrentData(absPath))
.thenThrow(Exception.class);
.thenThrow(RuntimeException.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but I am not sure what does this method test. ZookeeperClient.hasPath(String path) is not used in production.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just fixed this test to work with new mockito lib version. I suspect this test checks if exception thrown from client.getCache().getCurrentData(absPath) method will be wrapped in DrillRuntimeException.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, this method needs to be changed to test ZookeeperClient.hasPath(String path, boolean consistent). It is OK to do it in this PR or in a separate commit/JIRA/PR. If you decide to do it in a separate commit, please file JIRA.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do it in different PR.

@BeforeClass
public static void init() throws Exception {
MockUp<UUID> uuidMockUp = mockRandomUUID(session_id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is hard to say whether you want to change test logic or need a workaround for the removed MockUp.tearDown() method. In the latter case consider adding getTemporaryName() method to the UserSession class. The method can delegate to UUID.randomUUID() in production and can be mocked to return a predefined value in tests like this one. It will allow not to mock UUID.randomUUID() at all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to re-write this part since MockUp.tearDown() is not present in newer jmockit version. Now we don't mock session id at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UUID.randomUUID() was mocked both for session id and temporary table name. Can you get temporary table name the same way you do it for session id?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, from user session: userSession.resolveTemporaryTableName(tableName).

}
String temporaryTableName = "temporary_table_outside_of_default_workspace";

thrown.expect(UserRemoteException.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider introducing a new method to set thrown and message, something like void expectUserRemoteExceptionWithMessage(String message).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


thrown.expect(UserRemoteException.class);
thrown.expectMessage(containsString(String.format(
"VALIDATION ERROR: A table or view with given name [%s]" +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and possibly expectUserRemoteExceptionWithTableExistsMessage(String tableName, String schemaName).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

test("drop view %s.%s", DFS_TMP_SCHEMA, temporaryTableName);
}

private static String getSessionId() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider mocking getSessionId() in the UserSession. This method needs to be tested by itself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-written, it turned out I have access to UserSession through DrillbitContext, so no mock is required at all.

return cli.doMain(params.toArray(new String[params.size()]), projectDir, System.out, System.err);
}

private static Logger setupLogger(String string, Level logLevel) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, since I want to output info level logging when creating jars. Otherwise, no logs on how jars were created will appear.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should depend on log level and not be forcefully overridden. To debug, you have two options, either add "-X" to maven arguments or change log level for that logger in logback-test.xml.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, removed.

String sourceName = JarUtil.getSourceName(binaryName);
URL[] urls = {jars.resolve(binaryName).toUri().toURL(), jars.resolve(sourceName).toUri().toURL()};
File projectDir = dirTestWatcher.makeSubDir(Paths.get("drill-udf"));
FileUtils.copyDirectory(getResourceFile(Paths.get(projectDir.getName())), projectDir);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using <directory> in pom.xml if the goal here is to avoid creating files in the src path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks.


<properties>
<jar.finalName>${project.name}</jar.finalName>
<drill.version>1.13.0</drill.version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it OK to use old version? Does Drill support semver API compatibility for UDFs? If yes, how is it enforced? If no, compilation may fail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My previous jars used 1.8.0. for example, since I created them when that only version was available. API for Drill UDFs is public so we should not change it. There is no enforcement though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a reason not to use the SNAPSHOT version? Let's say somebody wants to introduce a new API for UDF and test it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used DrillVersionInfo.getVersion() to pas current Drill project version to the build.

@arina-ielchiieva
Copy link
Member Author

@vrozov addressed code review comments in the new commit. Please review.

@Category(SqlFunctionTest.class)
public class FunctionInitializerTest {

private static final String CLASS_NAME = "com.drill.udf.CustomLowerFunction";
@ClassRule
public static final BaseDirTestWatcher dirTestWatcher = new BaseDirTestWatcher();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, missed that in my previous review. Consider using TemporaryFolder rule instead of BaseDirTestWatcher.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, fixed.


JarBuilder jarBuilder = new JarBuilder("src/test/resources/drill-udf");
int result = jarBuilder.build(binaryName, buildDirectory.getAbsolutePath(), "**/CustomLowerFunction.java", null);
assertEquals("Build should be successful.", 0, result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving assertEquals inside JarBuilder.build() and returning name of the binary jar instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

throw e;
}

expectUserRemoteExceptionWithTableExistsMessage(temporaryTableName, DFS_TMP_SCHEMA);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it between test calls. My understanding the first one should succeed without exception.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely the same pattern applies to other unit tests with mulitple calls to test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, fixed.

<groupId>org.apache.drill.exec</groupId>
<artifactId>drill-java-exec</artifactId>
<version>${drill.version}</version>
<scope>compile</scope>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be provided?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* @return build exit code, 0 if build was successful
*/
public int build(String jarName, String buildDirectory, String includeFiles, String includeResources) {
System.setProperty("maven.multiModuleProjectDirectory", projectDirectory);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if the property already set. It probably will be good to restore the property to it's original value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@arina-ielchiieva
Copy link
Member Author

@vrozov addressed code review comments in two new commits. Please review.

Copy link
Member

@vrozov vrozov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM (see few minor comments). Rebase and squash to two commits.

throw e;
}

expectUserRemoteExceptionWithTableExistsMessage(temporaryTableName, DFS_TMP_SCHEMA);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this one too.

assertEquals("Build should be successful.", 0, result);
return jarName + ".jar";
} finally {
if (originalPropertyValue != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider originalPropertyValue != null ? System.setProperty(MAVEN_MULTI_MODULE_PROJECT_DIRECTORY, originalPropertyValue) : System.clearProperty(MAVEN_MULTI_MODULE_PROJECT_DIRECTORY);.

@@ -101,6 +110,9 @@ public static void setup() throws IOException {
fsUri = getLocalFileSystem().getUri();
}

@Rule
public ExpectedException thrown = ExpectedException.none();

@Rule
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the rule necessary? Can @After provide the same functionality? If yes, please file JIRA.


@BeforeClass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider @Before instead of calling it from reset().

@arina-ielchiieva
Copy link
Member Author

@vrozov done all the changes. One point regarding using current drill snapshot version as dependency when building jars. I decided to leave it hard-coded to 1.13.0 because mvn clean install with clean local maven repo succeeds since 1.13.0 is downloaded from central mvn repo. If we use 1.14.0-SNAPSHOT, build fails because this version is absent in central maven repo and in local, in other words to run unit tests on new snapshot version, we would have to build the project first which is not acceptable especially since currently we are not obliged to it.
Left the following comment in code, in case somebody what to build jars with current version:

      // uncomment to build with current Drill version
      // params.add("-Ddrill.version=" + DrillVersionInfo.getVersion());

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TemporaryTablesAutomaticDropTest extends BaseTestQuery {

private static final String session_id = "sessionId";

private static FileSystem fs;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can fs and permissions be declared as local variables?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, they are defined in @BeforeClass and the same for all tests.

@Before
public void setup() throws Exception {
public void setup() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to mock UUID?. You mentioned that there is a way to get sessionId from UserSession.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, yes. It turned out that there is no good way to retrieve session information in tests. Sorry for confusion.

import java.util.UUID;

import static org.apache.drill.exec.util.StoragePluginTestUtils.DFS_TMP_SCHEMA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TemporaryTablesAutomaticDropTest extends BaseTestQuery {

private static final String session_id = "sessionId";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case mocking is required, consider replacing String with UUID: uuid = UUID.nameUUIDFromBytes(session_id.getBytes())

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced it to UUID.randomUUID().

assertEquals("Build should be successful.", 0, result);
return jarName + ".jar";
} finally {
if (originalPropertyValue != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix indentation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

public String build(String jarName, String buildDirectory, String includeFiles, String includeResources) {
String originalPropertyValue = null;
try {
originalPropertyValue = System.setProperty(MAVEN_MULTI_MODULE_PROJECT_DIRECTORY, projectDirectory);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not split into two assignments. Move System.setProperty() call outside of try/finally block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member Author

@arina-ielchiieva arina-ielchiieva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vrozov done the changes.

public String build(String jarName, String buildDirectory, String includeFiles, String includeResources) {
String originalPropertyValue = null;
try {
originalPropertyValue = System.setProperty(MAVEN_MULTI_MODULE_PROJECT_DIRECTORY, projectDirectory);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

import java.util.UUID;

import static org.apache.drill.exec.util.StoragePluginTestUtils.DFS_TMP_SCHEMA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TemporaryTablesAutomaticDropTest extends BaseTestQuery {

private static final String session_id = "sessionId";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced it to UUID.randomUUID().

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TemporaryTablesAutomaticDropTest extends BaseTestQuery {

private static final String session_id = "sessionId";

private static FileSystem fs;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, they are defined in @BeforeClass and the same for all tests.

@Before
public void setup() throws Exception {
public void setup() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, yes. It turned out that there is no good way to retrieve session information in tests. Sorry for confusion.

assertEquals("Build should be successful.", 0, result);
return jarName + ".jar";
} finally {
if (originalPropertyValue != null) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

@vrozov vrozov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@arina-ielchiieva arina-ielchiieva force-pushed the DRILL-6272 branch 2 times, most recently from b5fc082 to 166a7b1 Compare May 11, 2018 15:36
@amansinha100
Copy link

@arina-ielchiieva I encountered some merge conflicts. Can you pls rebase on master ?

@arina-ielchiieva
Copy link
Member Author

@amansinha100 done.

@@ -47,7 +48,7 @@
.indexOf("-agentlib:jdwp") > 0;

public static TestRule getTimeoutRule(int timeout) {
return IS_DEBUG ? new TestName() : new Timeout(timeout);
return IS_DEBUG ? new TestName() : new Timeout(timeout, TimeUnit.MILLISECONDS);
Copy link
Member

@vrozov vrozov May 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timeout.millis(timeout)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, fixed.

@vrozov
Copy link
Member

vrozov commented May 12, 2018

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants