Problem:
Any method in Google's SDK android.jar throws an exception. So you couldn't use it for unit testing.
Solution:
Stephen Ng wrote good article with partial solution: "Manually remove throw statements in needed classes".
sites.google.com/site/androiddevtesting/
This is very expensive-in-time and boring work. We can automatize the removal.
But first of all we need sources of android.jar
Here the instruction how to download android sources and build them: source.android.com/source/download.html
After make completes you can find stubs sources for android.jar in directory like:
/way_to_you_android_source/out/target/co
I used them instead of real sources, because they already in one place.
So, let's start:
First try
- remove all throw statements from method body.
disadvantages:
- some method has non-void return type
- some class has no standard constructor
Second try:
- remove all throws statements and add
- return 1 to byte, short, int, long, float, double, char methods
- return false for boolen methods
- null for other object methods
- save super() calls in constructors
- some classes have final uninitialized fileds. Oh! I forgot them.
third try:
- set byte, short, int, long, float, double, char fields to 0
- set boolean fields to false
- set other fields to null
disadvantages:
- some classes have static{} section with initalization of final fields
fourth try:
- initialize uninitialized fields only
This is it.
Totally
- remove all throws statements and add
- return 1 to byte, short, int, long, float, double, char methods
- return false for boolen methods
- null for other object methods
- save super() calls in constructors
- set byte, short, int, long, float, double, char fields to 1
- set boolean filds to false
- set other fields to null
Useful links to Java language specification:
Method declaration: java.sun.com/docs/books/jls/third_editio
Constructor Declaration: java.sun.com/docs/books/jls/third_editio
Field declaration: java.sun.com/docs/books/jls/third_editio
Than I imported parced sources to eclipse project, build them and created android.jar in console.
Here you can download it: www.4shared.com/get/KWwSl5an/android.htm
#!/usr/bin/perl -w
use utf8;
binmode STDOUT, ":utf8";
binmode STDIN, ":utf8";
@targets = ();
@targetdirs = ();
foreach $parameter (@ARGV){
chomp $parameter;
if (not -d $parameter){
print "You have passed not a dir. Script gets dir only and performs processing.\n";
exit;
}
push @targetdirs,$parameter;
}
while($dirname = shift @targetdirs){
opendir $dir, $dirname;
while( $file = readdir $dir){
$full=$dirname."/".$file;
if (-d $full and $file ne "." and $file ne ".."){
push @targetdirs,$full;
next;
}
if($file =~ m/^.+\.java$/){
push @targets,$full;
}
}
}
my $BracketsN;
$BracketsN = qr/ (?> [^()]+ | \( (??{ $BracketsN }) \) )* /x;
my $BracesN;
$BracesN = qr/ (?> [^{}]+ | \{ (??{ $BracesN }) \} )* /x;
my $AngelBracketsN;
$AngelBracketsN = qr/ (?> [^<>]+ | < (??{ $AngelBracketsN }) > )* /x;
$NotReserved = qr/(?!abstract\s+|assert\s+|boolean\s+|b reak\s+|byte\s+|case\s+|catch\s+|char\s+ |class\s+|const\s+|continue\s+|default\s+ |do\s+|double\s+|else\s+|enum\s+|extends\s+ |false\s+|final\s+|finally\s+|float\s+|f or\s+|goto\s+|if\s+|implements\s+|import\s+ |instanceof\s+|int\s+|interface\s+|long\s+ |native\s+|new\s+|null\s+|package\s+|pri vate\s+|protected\s+|public\s+|return\s+ |short\s+|static\s+|strictfp\s+|super\s+ |switch\s+|synchronized\s+|this\s+|throw\s+ |throws\s+|transient\s+|true\s+|try\s+|v oid\s+|volatile\s+|while\s+)/;
$JavaLiteral = qr/\s+$NotReserved(?:\w|_|\$)[.\$_\w\d]* /;
$JL = $JavaLiteral;
$exception = qr/(?:\s+throws $JL)?/x;
$array = qr/(?:\s*\[\s*\])?/;
$typeParameters = qr/(?:\<$AngelBracketsN\>)?/;
$superClass = qr/(?:\s+extends$JL$typeParameters)?/;
$superInterface = qr/(?:\s+implements$JL$typeParameters(?: \s*,\s*$JL$typeParameters)*)?/;
undef $/;
while($name = shift @targets){
#Ищем повторяющиеся слова.
open my $in, "<:encoding(UTF-8)", $name or die "can't read file $name\n";
$text = <$in>;
close $in or die "can't close $name after read\n";
#Looking for class names
@classNames = ();
findClass($text, \@classNames);
my %constructor = ();
foreach $classname (@classNames){
while($text =~ m{((? $start=$1;
$body=$2;
$super="";
if($body =~ m/.*(super\s*\($BracketsN\)).*/gxs){
$super=$1.";";
}
$body=$super;
$constructor{ $start } = $body;
}
}
$ret="return null;";
$text =~ s/([.\$_\d\w]+$array$JL\s*\([^()]*\)$arr ay$exception\s*\{)($BracesN)(\})/$1$ret$3/g x;
$ret="return false;";
$text =~ s{((?:^|\s+)boolean$JL\s*\([^()]*\)$exce ption\s*\{)($BracesN)(\})}{$1$ret$3}xg;
$ret="return;";
$text =~ s{((?:^|\s+)(?:void)$JL\s*\([^()]*\)$exc eption\s*\{)($BracesN)(\})}{$1$ret$3}xg;
$ret="return 0;";
$text =~ s{((?:^|\s+)(?:byte|short|int|long|float|d ouble|char)$JL\s*\([^()]*\)$exception\s* \{)($BracesN)(\})}{$1$ret$3}xg;
while ( my ($key, $value) = each(%constructor) ) {
$key =~ s/\./\\\./gx;
$key =~ s/\(/\\\(/gx;
$key =~ s/\)/\\\)/gx;
$key =~ s/\{/\\\{/gx;
$key =~ s/\[/\\\[/gx;
$key =~ s/\]/\\\]/gx;
$key =~ s/\?/\\\?/gx;
$text =~ s/((? }
my $NotIntializedInStatic="";
@inStatic = ();
while($text =~ m/static\s*\{($BracesN)\}/gx){
$static = $1;
while($static =~ m/\s*([.\$_\w\d]+)\s*=\s*[.\$_\w\d]+(?=; |\s)/gx){
push @inStatic, $1;
}
}
if(scalar(@inStatic) > 0){
$list="";
foreach $initialized (@inStatic){
$list .= $initialized . "|";
}
chop $list;
$NotIntializedInStatic = qr/(?!$list)/;
}
#Need to initialize uninitialized final variables
$text =~ s/(final(?:\s+(?:transient|static))* \s+ boolean \s+ $NotIntializedInStatic[.\$_\w\d]* \s*);/$1=false;/gx;
$text =~ s/(final(?:\s+(?:transient|static))* \s+ (?:byte|short|int|long|float|double|char) \s+ $NotIntializedInStatic[.\$_\w\d]* \s*);/$1=0;/gx;
$text =~ s/(final(?:\s+(?:transient|static))* \s+
(?!boolean\s+|byte\s+|short\s+|int\s+|lo ng\s+|float\s+|double\s+|char\s+)[.\$_\d\w] +
$typeParameters $array \s+ $NotIntializedInStatic[.\$_\w\d]* \s*);/$1=null;/gx;
open my $out, '>:encoding(UTF-8)', $name or die "can't open file $name for write\n";
print $out $text;
close $out or die "couldn't write to $name\n";
}
sub findClass{
my ($text, $classNames) = @_;
#Java classes looks like:
#ClassModifiers_opt class Identifier TypeParameters_opt Super_opt Interfaces_opt
while($text =~ m/class($JL) $typeParameters $superClass $superInterface\s*\{($BracesN)\}/gx){
$classname = $1;
$classbody = $2;
$classname =~ s/\s//gx;
push @$classNames, $classname;
#looking for inner classes
findClass($classbody, \@$classNames);
}
}
Script gets path to folder with sources, reads all *.java files, edits them and saves back.
example of call:
script.pl /way_to_you_android_source/android_stubs
Questions to myself for future investigation:
- How does initial stubs with throws generate from real code?
- How to automatically build android.jar in script?
- Which sources I need? Can I download them in script?