Thursday Apr 12, 2012

Tip #15: How To Debug Unit Tests During Maven Builds

It must be really really hard to step through unit tests in a debugger during a maven build.  Right?

Wrong!

Here is how i do it:

1) Set up these environmental variables:

MAVEN_OPTS=-Xmx1024m -Xms256m -XX:MaxPermSize=512m
MAVEN_OPTS_DEBUG=-Xmx1024m -Xms256m -XX:MaxPermSize=512m  -Xdebug (no line break here!!)  -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9999
MAVEN_OPTS_REG=-Xmx1024m -Xms256m -XX:MaxPermSize=512m

2) create 2 scripts or aliases like so:

 maveny.bat:

set MAVEN_OPTS=%MAVEN_OPTS_DEBUG%

mavenn.bat:

set MAVEN_OPTS=%MAVEN_OPTS_REG%

 


 To debug do this:

  1. run maveny.bat
  2. run mvn install
  3. attach your debugger to port 9999 (set breakpoints of course)
  4. When maven gets to the unit test phase it will hit your breakpoint and wait for you.

When done debugging simply run mavenn.bat

Notes

  1. If it takes a while to do the build then you don't really need to set the suspend=y flag.
  2. If you set the suspend=n flag then you can just leave it -- but only one maven build can run at a time because of the debug port conflict.



Friday Jan 06, 2012

Tip #14 Symbolic Links on Windows?!?

Windows does have real UNIX-like symbolic links.  They are called junctions.  There are no built-in Windows tools for using them though. 

SysInternals has a great tool for using them.  Note that Microsoft owns SysInternals now.

Try it: http://technet.microsoft.com/en-us/sysinternals/bb896768


Thursday Nov 10, 2011

Tip #13 java.io.File Surprises

There is an assumption that I've seen in code many times that is totally wrong.  And this assumption can easily bite you.  The assumption is:

File.getAbsolutePath and getAbsoluteFile return paths that are not relative.

 Not true!  Sort of.  At least not in the way many people would assume.  All they do is make sure that the beginning of the path is absolute.  The rest of the path can be loaded with relative path elements.  What do you think the following code will print?

public class Main {
    public static void main(String[] args) {
        try {
            File f = new File("/temp/../temp/../temp/../");
            File abs  = f.getAbsoluteFile();
            File parent = abs.getParentFile();
            System.out.println("Exists: " + f.exists());
            System.out.println("Absolute Path: " + abs);
            System.out.println("FileName: " + abs.getName());
            System.out.printf("The Parent Directory of %s is %s\n", abs, parent);
            System.out.printf("The CANONICAL Parent Directory of CANONICAL %s is %s\n",
                        abs, abs.getCanonicalFile().getParent());
            System.out.printf("The CANONICAL Parent Directory of ABSOLUTE %s is %s\n",
                        abs, parent.getCanonicalFile());
            System.out.println("Canonical Path: " + f.getCanonicalPath());
        }
        catch (IOException ex) {
            System.out.println("Got an exception: " + ex);
        }
    }
}

Output:

Exists: true
Absolute Path: D:\temp\..\temp\..\temp\..
FileName: ..
The Parent Directory of D:\temp\..\temp\..\temp\.. is D:\temp\..\temp\..\temp
The CANONICAL Parent Directory of CANONICAL D:\temp\..\temp\..\temp\.. is null
The CANONICAL Parent Directory of ABSOLUTE D:\temp\..\temp\..\temp\.. is D:\temp
Canonical Path: D:\

Notice how it says that the parent of d:\ is d:\temp !!!The file, f, is really the root directory.  The parent is supposed to be null.

I learned about this the hard way! getParentXXX simply hacks off the final item in the path. You can get totally unexpected results like the above. Easily.

I filed a bug on this behavior a few years ago[1].  

Recommendations:

(1) Use getCanonical instead of getAbsolute.  There is a 1:1 mapping of files and canonical filenames.  I.e each file has one and only one canonical filename and it will definitely not have relative path elements in it.  There are an infinite number of absolute paths for each file.

(2) To get the parent file for File f do the following instead of getParentFile:

File parent = new File(f, "..");

[1] http://bt2ws.central.sun.com/CrPrint?id=6687287



Wednesday Oct 12, 2011

Tip#12 Waste of time setting instance variables to their default.

I see this code frequently:

class foo {

private String s1 = null;
private boolean b1 = false;
int i1 = 0;
....
}

This is a waste of time.  Those variables are NOT on the stack.  They are always guaranteed to be equal to the default that is defined for that data type.  By setting them to a value, special code has to be written and run automatically when the instance is instantiated.  E.g. the String is initially set to null automatically.  Then code is run which once again sets it to null.

 

class foo {
private String s1;
private boolean b1;
int i1;
....
}

==============

On the other hand you have to set initial values for local variables.  There storage space is allocated on the fly and they are on the stack.  Therefore they are initialized to whatever garbage happens to be on the stack.


Tuesday Oct 04, 2011

Tip #11 Subversion Low-Tech Safety Net

Have you ever lost a significant  amount of source code?  If you have you probably already have multiple layers of safety nets.  This stuff is just too hard to do to take a chance on hardware failures, hickups, etc.  

E.g. I remember once I thought I was in my temp directory.  I entered this command:

rm -rf *

Oops.  I was in the root directory!  I.e. stuff indeed happens. 

Today's excruciatingly simple tip:

When you are actively developing code but it is not yet ready to be checked-in.  Yet you have spent a significant chunk of time on it.  I do this:

svn status | sort | tee dome.bat

I edit the dome.bat file and change this:

?       comm-util-3.1.2.save.jar
?       d
?       diffs.txt
?       dome.bat
?       q
A       src\main\java\com\sun\enterprise\universal\process\WindowsCredentials.java
A       src\main\java\com\sun\enterprise\universal\process\WindowsException.java
A       src\main\java\com\sun\enterprise\universal\process\WindowsRemoteAsadmin.java
A       src\main\java\com\sun\enterprise\universal\process\WindowsRemotePinger.java
A       src\main\java\com\sun\enterprise\universal\process\WindowsRemoteScripter.java
A       src\main\java\com\sun\enterprise\util\io\WindowsRemoteFile.java
A       src\main\java\com\sun\enterprise\util\io\WindowsRemoteFileCopyProgress.java
A       src\main\java\com\sun\enterprise\util\io\WindowsRemoteFileSystem.java
M       pom.xml
M       src\main\java\com\sun\enterprise\universal\glassfish\TokenResolver.java

to this:

       jar cvfM comm-util-3.1.2.save.jar src\main\java\com\sun\enterprise\universal\process\WindowsCredentials.java src\main\java\com\sun\enterprise\universal\process\WindowsException.java src\main\java\com\sun\enterprise\universal\process\WindowsRemoteAsadmin.java src\main\java\com\sun\enterprise\universal\process\WindowsRemotePinger.java src\main\java\com\sun\enterprise\universal\process\WindowsRemoteScripter.java src\main\java\com\sun\enterprise\util\io\WindowsRemoteFile.java src\main\java\com\sun\enterprise\util\io\WindowsRemoteFileCopyProgress.java src\main\java\com\sun\enterprise\util\io\WindowsRemoteFileSystem.java pom.xml src\main\java\com\sun\enterprise\universal\glassfish\TokenResolver.java

Now  I simply run the script and email the jar file to my work email account.  My company will be happy to guarantee safety for my jar file. 


Friday Sep 30, 2011

Tip#10 Technique for making your class Immutable

Immutable classes are great.  You never have to worry about concurrency problems.  It also makes the code more readable - if a variable is final you don't have to look around for the other places where it gets modified -- there aren't any. Also the compiler will help you.  If you forget to assign a final variable it's a compile error.  It's also an error to try and reassign it.

Here is a simple trivial technique for setting final variables from a constructor where you need to do processing first.  The point is that you use temp variables to get your final values all lined up.  Then you assign the final values once.

I'll just let the code write the rest of this blog:

public final class DcomInfo {
    private final WindowsCredentials credentials;
    private final Node node;
    private final String password;
    private final String host;
    private final String user;
    private final String windowsDomain;
    private final String remoteNodeRootDirectory;

    public DcomInfo(Node theNode) throws WindowsException {
        node = theNode;

        if (node == null)
            throw new WindowsException(
                    Strings.get("internal.error", "Node is null"));

        if (!isDcomNode(node))
            throw new WindowsException(Strings.get("not.dcom.node",
                    getNode().getName(), getNode().getType()));

        SshConnector conn = node.getSshConnector();
        if (conn == null)
            throw new WindowsException(Strings.get("no.password"));

        SshAuth auth = conn.getSshAuth();
        if (auth == null)
            throw new WindowsException(Strings.get("no.password"));

        String notFinal = auth.getPassword();
        if (!ok(notFinal))
            throw new WindowsException(Strings.get("no.password"));

        password = DcomUtils.resolvePassword(notFinal);

        notFinal = node.getNodeHost();
        if (!ok(notFinal))
            notFinal = conn.getSshHost();
        if (!ok(notFinal))
            throw new WindowsException(Strings.get("no.host"));
        host = resolver.resolve(notFinal);

        notFinal = auth.getUserName();
        if (!ok(notFinal))
            notFinal = System.getProperty("user.name");
        if (!ok(notFinal))
            throw new WindowsException(Strings.get("no.username"));
        user = resolver.resolve(notFinal);

        notFinal = node.getWindowsDomain();
        if (!ok(notFinal))
            notFinal = host;
        windowsDomain = resolver.resolve(notFinal);

        notFinal = node.getInstallDirUnixStyle();
        if (!ok(notFinal))
            throw new WindowsException(Strings.get("no.lib.dir"));

        if (!notFinal.endsWith("/"))
            notFinal += "/";

        notFinal += SystemPropertyConstants.getComponentName();
        remoteInstallRoot = StringUtils.quotePathIfNecessary(notFinal);
        notFinal += "/lib";
        notFinal = StringUtils.quotePathIfNecessary(notFinal);
        notFinal = notFinal.replace('/', '\\');
        nadminParentPath = notFinal;
        nadminPath = notFinal + "\\nadmin.bat";

        String notFinal2 = node.getNodeDirAbsolute();

        if (notFinal2 == null) {
            // no special nodedir -- use the defaults
            notFinal2 = remoteInstallRoot;  // e.g. "d:/glassfish3/glassfish"
            notFinal2 += "/nodes";
        }
        notFinal2 = notFinal2.replace('/', '\\');

        if (!notFinal2.endsWith("\\"))
            notFinal2 += '\\';

        remoteNodeRootDirectory = notFinal2 + node.getName();

        credentials = new WindowsCredentials(getHost(), getWindowsDomain(),
                getUser(), getPassword());
    }

Wednesday Sep 28, 2011

Tip #9 - Advanced Debugger Attach

GlassFish has server-side commands, for instance create-instance, that in-turn call the client side.  It generally does this by calling asadmin's cousin:

glassfish/lib/nadmin[.bat]

with the command. 

But what if you need to debug the spawned client JVM?  Impossible?  No!  Trivial!

Simply do this:

edit nadmin (or nadmin.bat if you are using Windows) and add the java debugging args.

e.g.

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1234

 Now simply run the server-side command and then leisurely attach a debugger to port 1234 (make sure you have a breakpoint waiting or you'll miss out). 

If you use NetBeans you can easily debug both server and client at the same time.

Don't forget to undo nadmin when you are done! 


Tuesday Sep 27, 2011

Tip #8 Watch Those RegEx calls -- they are S-L-O-W

I see this construct quite a bit:

String someString ....
someString.replaceAll("\\\\","/");

This is probably a huge waste of time (probably because I didn't time it).  Not only does it do much much much more complex regular expression parsing, for no good reason, it is also much less readable then the "correct" way to do it:

String someString ....
someString.replace('\\','/');


Monday Sep 26, 2011

Tip #7 - Absolute Filenames in Java

I'm busy reading lots and lots of code lately because I need to implant DCOM support.  I just saw this:

  if (nodeDirFile.isAbsolute()) {
                return nodeDir;
            }

Would this surprise you?

File f = new File("/foo/../foo/.././././././foo/././././././../foo/goo");

f.isAbsolute()
will return true.  That is an absolute path as far as Java IO is concerned.  I.e. if it BEGINS with something absolute -- then it is absolute.  Even if it's really really ugly.

What was probably desired is getCanonical().  There is one and only canonical filenames per file.  There are infinite "absolute" filenames per file.


Monday Aug 15, 2011

Tip #6 Windows Script to change directory QUICKLY

GlassFish's source tree, like any huge software has enormously long directory paths.  My goal is to try to avoid ever typing them in. Even with the shell auto-filling it still takes too much time, IMHO.

I created 2 scripts on my Windows systems:

1)  "cdn.bat"

cd \gf\main\nucleus\*%1*\*%2*

2) "cda.bat"

cd \gf\main\appserver\*%1*\*%2*

==============

If I want to change my directory to,say flashlight-framework I just do this:

cdn fl fr

This command instantly moves me to cluster-cli

cdn clu cl



Friday Aug 12, 2011

Tip #5 Avoid RegEx When Unnecessary

I see this in GlassFish source code frequently:

string = string.replaceAll("\\\\", "/");

 Whoa!  FOUR backslashes?!?  Yes.  That's a regular expression.  What we see is FOUR but inside the string itself are TWO literal actual backslashes. The backslashes have to be doubled so that the RegEx parser knows that we are looking for a literal backslash.

What the author of this code really wants to do is something like this:

c:\a\b\c\d  ==>  c:/a/b/c/d

 This can easily be done without the more confusing regex notation and (I assume) the slower regex code like so:

string = string.replace('\\', '/');


Tuesday Aug 09, 2011

Tip #4: Stop and Think before you make that Class public

I run into this time-wasting situation fairly frequently:

I need to change the signature of a Java class, in GlassFish,  that has been declared public.  Uh-oh!  It's public so I can't just change it. 

So -- my first step is to grep through every single java file in GlassFish.  That means  7,772 files currently!

Now I have to wonder -- is the class being used from modules outside of GlassFish?  Uh-oh -- now I have to run massive automated tests to make sure I don't break something.

Ultimately you can never be totally sure that public class is not being used somewhere.  E.g. say the name of the class is FooGooHoo.  What if there is code doing a Class.forName("Foo" + "Goo" + "Hoo") ?  I'll never find it with a simple search. 

This is all expected stuff with the public keyword.  But what if the class is exclusively used inside of its own package?  Then I just  wasted a whole lot of my employer's money chasing down non-existent references. 

On the other hand if the class had its visibility set to pkg-private in the first place, all I had to do was to search inside that one package for uses.  Java guarantees no other package can see it.  And if other code somehow subverts that policy -- tough!!

Moral of the Story:

Only mark classes and methods as public when you are absolutely positively sure that it must be public.  When in doubt make it pkg-private and avoid protected too.


Monday Aug 08, 2011

Tip #3: Make Life Easier for Your Code Reviewer

There are probably a hundred applications that will package up code changes in various cool ways.  You could also write your own -- unfortunately it's one of those cases where the time you save using the tool will never approach the time you spent making the tool. 

I get code review requests fairly often and they are almost always like this:

 Below are the diffs for my change.  Please review

 And as promised the output of some diff tool is below in the email.  What if the context around the diffs isn't big enough?  What if I hate the diff tool formatting?  What if I want to compile and run the actual changes in my environment?

I can't do any of the above quickly and easily with the raw diffs embedded in the email.  Even copying and pasting them is usually a big pain because of the prepended plus signs.

 Here are my requirements for code review submitters (Please?!?)

  • Send out the actual changed source files
    • Make it very very easy to get those source file into the right directory.  Don't make me type in commands with endlessly long paths!
  • Send me the diffs as a file and have it appear directly in the email (maybe the change is easy and I don't need to waste time dealing with file attachments)

And here is the solution I use.  It is simple and old-fashioned and works great:

  1.  Create the following script (Windows version).  I use this script constantly when I'm working on source code.  That's why I named it "s.bat".  Usually I don't use the "dome.bat" file but I always create it just in case.  I sort the output to get all the modified files together.

    svn status | sort | tee dome.bat

  2. To submit the changes to a code reviewer, I want him or her to get the diffs file, and all changed files.  I collect the diffs in a file like so (make a script out of it!)
    svn diff > diffs.txt
  3. Here is the trick.  Edit the dome.bat file from step 1 above.  Delete the uninteresting lines and QUICKLY edit the Modified or Added lines to do this:
    jar cvfM mychanges.jar diffs.txt c:/some/gigantic/ugly/long/path/foo.java c:/some/even/uglier/gigantic/long/path/foo2.java
  4. Now I attach mychanges.jar to an email and also copy the diffs to the email message.  I tell the reviewer which directory to change to and how to unpack the source files.  E.g.
    "cd to core/kernel and run jar xvf mychanges.jar to overlay my changes in your installation"


It sounds like a big deal but it shouldn't take more than a couple minutes and it makes life more pleasant for the reviewer.  Note how the reviewer can do this:

jar cvfM temp.zip src
jar xvf mychanges.jar
run his or her favorite diff tools
compile and build, look at the code in a debugger or whatever
when review is complete, jar xvf temp.zip


Thursday Jul 28, 2011

Tip #2 Step Through Every Line of Code in a Debugger

This is so simple.  The place where I catch most of my bugs -- before the code is even checked-in.  

Drum-roll:

Step Through Every Line of Code You Write in a Debugger

 I'm frequently amazed at what I bump into during this pass-through.  I've found all sorts of embarrassing problems.  You never see them, though, because I caught them before checking in.

Nobody writes bug-free code.  But you can check-in bug-free (or nearly so) code.

Some people may counter with: "Oh ho!  I have no time to step through the code in a debugger!"  My answer is that the time it takes to  step through the code in a debugger is normally a tiny percentage of the time it takes to do all of these things:

  • Think up what to write
  • Write it
  • Fix compile errors etc.
  • Fix more obvious errors from simple tests
  • write unit tests
  • write dev tests
  • close an issue in Issue Tracker
  • check-in comments
  • Ask for a code review, get comments, respond. make changes

And I mean every line of code.  Modern java debuggers allow you to change values of variables, etc., to force your execution path into error handling code.

If you start do this zealously it may force you into picking up other good development habits too, that make it easier to step-through all of your code.



About

ByronNevins

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today