mdoery ~ software development

adventures in coding

code coverage for node and vows

| Comments

Summary: you can get code coverage stats for your node.js code

(FYI my dev box uses Ubuntu Linux 13.10.)

I’ve been developing new features in my node.js application, and concurrently writing vows.js tests for the new code. Yesterday, I decided that it was about time that I check to see just how much code my tests are covering.

What to do? When developing Java software with the eclipse IDE, it’s relatively simple to get code coverage information for your JUnit tests. With node, things are more complicated.

I looked at code coverage in npm and saw a number of potentially useful libraries, but didn’t want to research each one to see if they worked with vows. Then I noticed that the vows page says “Code coverage reporting is available if instrumented code is detected. Currently only instrumentation via node-jscoverage is supported.” Well, that makes it a no-brainer. They give you instructions for downloading and installing node-jscoverage, which is not in npm!

While I was hunting around, I had found Alex Seville’s post about some problems he had encountered using node-jscoverage. That made me concerned that the instructions on the vows page were incomplete. So I followed his directions. In his step 2, I ran into a little permission trouble; I had to run each command as sudoer:

1
2
3
sudo ./configure
sudo make
sudo make install

From then on, almost everything worked. Here’s my node.js app code:

1
2
3
/srv/app/node-app
/srv/app/node-tests
/srv/app/node_modules

And here’s how I produced the instrumented code to test:

1
jscoverage /srv/app/node-app /home/mdoery/instrumented/node-app

Notice I put my instrumented code into a directory which is completely independent from my app directory structure. You don’t want instrumented code running in your app! It’s only used for generating coverage reports.

I followed Alex’s link to Jeff Kunkle’s post about switching between testing instrumented and non-instrumented code. I grabbed Jeff’s sample code to do this, and it worked like a charm. Then I ran a test –

1
2
3
4
5
6
7
8
9
10
11
12
/srv/app/node-tests$ ../node_modules/vows/bin/vows test-something.js
ERROR   => Error: Cannot find module 'moment'
  at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:280:25)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at exports.undefined.window.C (/home/mdoery/instrumented/node-app/C.js:208:22)
    at Object.<anonymous> (/home/mdoery/instrumented/node-app/C.js:607:3)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)

Oops! The instrumented code couldn’t find my app’s libraries (such as moment) in /srv/app/node_modules. So I created a soft link to these in my instrumented directory:

1
ln -s /srv/app/node_modules /home/mdoery/instrumented/node_modules

After doing that, my instrumented code appeared to run fine; I got the usual happy output from vows:

1
2
3
/srv/app/node-tests$ ../node_modules/vows/bin/vows test-something.js
·············
  ✓ OK » 13 honored (0.004s)

But how could I be sure that the instrumented code was being exercised? This time, I reran my test with the --cover-html option:

1
../node_modules/vows/bin/vows test-something.js --cover-html

Sure enough, I saw a file called coverage.html had been generated in my current directory. I opened it in my browser window to view an html display of what code is covered, and what still needs to be tested. Nice!

android example using GridLayout

| Comments

Summary: I demonstrate building a simple app which uses android.support.v7.widget.GridLayout

I’m trying out a GridLayout for one of my projects. I built a small app to test it. The app looks like this on my tablet:

Here’s how to do it.

First, create an Android project in Eclipse (File > New > Project… > Android Application Project). In setting up my project I used a Minimum Required SDK of 2.2 (Froyo) and Target SDK 4.2 (Jelly Bean), and otherwise kept all the default settings proposed by Eclipse.

Next, import five image files into the res/drawable folder. These will be displayed in the grid. You can grab these images from this page (right-click on the image and “Save as…”). They are all in the public domain.

Open activity_main.xml in “Graphical Layout” view, click on “Layouts” and drag a “GridLayout” onto the view. A dialog opens which says “Warning android.widget.GridLayout requires API level 14 or higher, or a compatibility library for older versions. Do you want to install the compatibility library?” – do that. Set “Use Default Margins” to true, and set the “Column Count” to 2 (these two items are found under the Properties panel):

When I did this last step, something weird happened. I saw a couple of errors in the xml:

Unexpected namespace prefix “app” found for tag android.support.v7.widget.GridLayout. The activity_main.xml had the property app:columnCount=“2” – I hadn’t added this manually. I hunted around the internet and found a bug report about it, including a workaround – adding the attribute tools:ignore=“MissingPrefix” to the RelativeLayout tag gets rid of the error.

Finally, set the Android id for the GridLayout to be android:id=“@+id/gridLayout1”

The rest is simple; add all the images programmatically to the GridLayout, and it’s done. This is all the code that’s in my MainActivity:

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
31
32
package com.mdoery.tut.gridlayout;

import android.os.Bundle;
import android.app.Activity;
import android.support.v7.widget.GridLayout;
import android.widget.ImageView;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      GridLayout gl = (GridLayout)findViewById(R.id.gridLayout1);
      setupGridLayout(gl);
  }

  private void setupGridLayout(GridLayout gl) {
      gl.addView(getImageView(R.drawable.apple));
      gl.addView(getImageView(R.drawable.ladybeetle));
      gl.addView(getImageView(R.drawable.moon));
      gl.addView(getImageView(R.drawable.sheep));
      gl.addView(getImageView(R.drawable.strawberry));
      
  }
  private ImageView getImageView(int resource) {
      ImageView iv = new ImageView(this);
      iv.setBackgroundResource(resource);
      return iv;
  }

}

where did my GridLayout go?

| Comments

Summary: The class android.support.v7.widget.GridLayout vanished from my Eclipse project! Here’s how I retrieved it.

I was working on an Android project in Eclipse a while back, when I got pulled off of it to do some contract work. When I finally returned to the project today, I was surprised to see it had this compiler error: “The import android.support.v7.widget cannot be resolved”.

Periodically I’m dumbfounded by things which happen in Eclipse, but I’ve begun to take surpises in a stride. I searched around the internet for posts which reported this problem and eventually found that the solution is to import a “support” library which is found in the Android SDK directory. For me, that jar file is located here: C:\Program Files\Android\android-sdk\extras\android\support\v7\gridlayout\libs\android-support-v7-gridlayout.jar

This is a quick rundown of the simple procedure I followed to solve the problem:

  1. Open Eclipse’s Navigator view (from the menu up top, select Window > Show view > Navigator)
  2. Right-click on the project’s libs directory
  3. From the menu which opens, choose “Import…” and then choose “File System”.
  4. In the “File System” dialog which opens, navigate to wherever your android-support-v7-gridlayout.jar has been stored. Like I said above, for me it was under C:\Program Files\Android\android-sdk\extras\android\support\v7\gridlayout\libs\android-support-v7-gridlayout.jar where presumably the Android SDK Manager placed it. Select the jar file, and click the Finish button.
  5. Tada! The compiler error goes away.

I don’t understand how this project broke while I wasn’t working on it. I can only imagine that the imported library was somehow deleted, but I don’t recall doing that, so it’s another one of those fun Android mysteries!

P.S. This evening, I discovered that I had added the jar file to a separate, Android library Eclipse project and had set that project as an Android reference for my current project (under Properties > Android). The library project had been closed some time between now and then. So that explains why the file was “missing;” it was never there to begin with!

working with json in android apps

| Comments

Summary: Extracting data from a JSON formatted file is pretty simple in Android.

I’m planning to store data in the JSON format for the app that I’m currently building. So here’s a quick post which demonstrates the procedure for reading JSON into data which can be used in your app.

  1. Create an Android application project called MainJson in Eclipse.
  2. Set the Minimum Required SDK property to API 8: Android 2.2 (Froyo), and the Target SDK to API 17: Android 4.2 (Jelly Bean). I tested this tutorial with these parameters.
  3. Set the package name to be com.mdoery.tut.json.
  4. Start the project with a blank Activity.
  5. Create a sub-directory called json under the assets folder; we’ll store our JSON file here.

Now that the project is ready, create a file with JSON-formatted data as follows:

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
{"sculptors": [
    {"sculptor": {
        "firstname": "Isamu",
        "lastname": "Noguchi",
        "sculptures": [
            {"sculpture": {"name": "Red Cube", "date": "1968"}},
            {"sculpture": {"name": "Black Sun", "date": "1969"}},
            {"sculpture": {"name": "Sky Gate", "date": "1977"}}
        ]
    }},
    {"sculptor": {
      "firstname": "David",
      "lastname": "Smith",
      "sculptures": [
        {"sculpture": {"name": "Medals for Dishonor", "date": "1937-40"}},
        {"sculpture": {"name": "Agricola I", "date": "1952"}},
        {"sculpture": {"name": "CUBI VI", "date": "1963"}}
      ]
    }},
    {"sculptor": {
      "firstname": "Rachel",
      "lastname": "Whiteread",
      "sculptures": [ {"sculpture": {"name": "House", "date": "1993"}} ]
    }}
  ]
}

Call the file sculptors.json, and add it to the assets/json folder.

Finally, open the MainActivity java class, which was created by default, and edit it to contain the following code:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.mdoery.tut.json;

import java.io.IOException;
import java.io.InputStream;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.text.Html;
import android.text.Spanned;
import android.widget.TextView;

public class MainActivity extends Activity {

  private static String sep = System.getProperty("line.separator");
  private static String br = "<br>";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView tv = (TextView)this.findViewById(R.id.textView1);
    try {
      // Read contents of file sculptors.json 
      String json = getText("json/sculptors.json",
        getApplicationContext()) + sep;
      // Display it in TextView
      displaySculptorNames(tv, json);
    } catch (Exception e) {
      // If there is an exception, show it in the TextView
      tv.setText("Exception: " + sep + e + sep);
    }
  }

  private String getText(String filename, Context ctx)
    throws IOException {
    InputStream is = ctx.getAssets().open(filename);
    // We guarantee that the available method returns 
    // the total size of the asset not more than 2G.
    int size = is.available();
    // Read the entire asset into a local byte buffer.
    byte[] buffer = new byte[size];
    is.read(buffer);
    is.close();
    String text = new String(buffer);
    return text;
  }

  /**
   * Extracts information from JSON text, and displays it in TextView 
   * @param tv TextView
   * @param json String of json from file sculptors.json
   * @throws JSONException
   */
  private void displaySculptorNames(TextView tv, String json)
    throws JSONException {
    String text = "";
    JSONTokener tokener = new JSONTokener(json);
    JSONObject object = (JSONObject) tokener.nextValue();
    JSONArray sculptors = object.getJSONArray("sculptors");
    for (int ii = 0; ii < sculptors.length(); ii++) {
      text += (ii + 1) + ") ";
      JSONObject obj = sculptors.getJSONObject(ii);
      JSONObject sculptor = obj.getJSONObject("sculptor");
      String firstname = sculptor.getString("firstname");
      String lastname = sculptor.getString("lastname");
      JSONObject sculpture = sculptor.getJSONArray("sculptures")
        .getJSONObject(0).getJSONObject("sculpture");
      String name = "<i>" + sculpture.get("name") + "</i>";
      text += firstname + " " + lastname + " sculpted " + name;
      String date = sculpture.getString("date");
      try {
        Integer.parseInt(date);
        text += " in " + date;
      } catch (NumberFormatException e) { // catch date range
        text += " during " + date;
      }
      text += br;
    }
    Spanned span = Html.fromHtml(text);
    tv.setText(span);
  }
}

If you run this in the emulator, you should see a screen with text created from the information extracted from the JSON file, like this:

As an aside, notice that some of the text is formatted in italics. That was done using Html.fromHtml in the code. I found out how to do that at Stealthcopter.com.

BTW, here’s a handy tip if you want to try this project with your own JSON: validate JSON before trying to read it in your app. It may save you some trouble! I used jsonlint to do so.

how to share resources among two or more android apps

| Comments

Summary: This post explains how to use the attribute sharedUserId to make data accessible from one Android app to another. To follow along, you should be familiar with building Android projects in Eclipse. To implement the solution, you will need an Android device; it won’t work in the emulator.

For the current app that I’m building, the user will be given the choice of downloading one or more “packs” which contain new content. Here, “content” means resources such as images or text files in the assets folder of an Android project. Every new pack will be implemented in its own, independent Android project.

As a concrete example, suppose I’ve built a crossword app which allows users to solve crosswords on a mobile device. The app comes with a limited supply of crossword puzzle content. If users want to solve more crosswords, they have to download more packs of puzzles from the Google Play store, or elsewhere. For a real-life example, take a look at Standalone’s Crossword Light app.

The key to making this work lies in the AndroidManifest.xml attribute called sharedUserId.

In keeping with the example above, open Eclipse, and first create the project MainCrossword. This project represents the app which lets users solve crosswords. Note that we won’t implement a real interface; for our purposes, we’ll just provide a simple TextView to display debugging information:

  1. This is a launchable application - meaning it will show up as an App when installed on your device. As you create the new project, accept all the defaults supplied by Eclipse. Pay attention to these options, in particular:
    1. Set the package name to be `com.mdoery.tut.maincrossword`.
    2. Set the Minimum Required SDK property to API 8: Android 2.2 (Froyo), and the Target SDK to API 17: Android 4.2 (Jelly Bean). This tutorial has been tested with that configuration.
    3. When creating the workspace, just choose a blank `Activity` to start with.
  2. Open the `AndroidManifest.xml` in `MainCrossword` and edit the <manifest> tag to add the following property: `android:sharedUserId=”com.mdoery.tut.maincrossword.uniqueid”`.

Next, create another project in Eclipse, called AnimalCrosswordPack. This will be a “data pack” which will contain the content for a number of animal-themed crosswords (note that for our example we will use fake data; we’re not going to build a real app, just enough of it to demonstrate the solution).

  1. Choose the same options as for `MainCrossword`, but this time, name the package `com.mdoery.tut.maincrossword.pack`. There’s nothing special about this package name; I’m just using a different package from the previous one so it’s clear that they don’t have to be the same.
  2. Otherwise, repeat the process as for the `MainCrossword` project (same Target SDK, etc.).
  3. In `AnimalCrosswordPack`, there should be a folder called `assets`. Create a subfolder here called `txt`, and then create the file `animal_theme.txt` within `assets/txt`. Edit this file to contain the text “Lions and tigers and bears, oh my!”.
  4. Open the `AndroidManifest.xml` in `AnimalCrosswordPack` and perform two edits:
    1. delete the <intent-filter> tag. This makes it impossible to launch the app. We don’t want this app to be launched by the user; it’s only there as a source of data to be accessed by the `MainCrossword` app.
    2. edit the <manifest> tag to add the same `sharedUserId`: `android:sharedUserId=”com.mdoery.tut.maincrossword.uniqueid”`.

So the AndroidManifest.xml file in AnimalCrosswordPack should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mdoery.tut.maincrossword.pack"
    android:versionCode="1"
    android:versionName="1.0"
    android:sharedUserId="com.mdoery.tut.maincrossword.uniqueid" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.mdoery.tut.maincrossword.pack.MainActivity">
        </activity>
    </application>
</manifest>

Notice the sharedUserId property. The MainCrossword project also has the android:sharedUserId="com.mdoery.tut.maincrossword.uniqueid" property set. However, it has not had <intent-filter> tag removed.

Eclipse should have created a file activity_main.xml in the directory MainCrossword/res/layout (if it hasn’t, add this now). Edit the file so that it contains a TextView to display the output of our test, as follows:

1
2
3
4
5
6
7
8
9
10
11
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minLines="25"/>
</RelativeLayout>

Now open com.mdoery.tut.maincrossword.MainActivity and edit the code as follows:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.mdoery.tut.maincrossword;

import java.io.InputStream;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.widget.TextView;

/**
 * Demo to show how data can be shared between two apps.
 * 
 * You must install the Android app generated from this project and 
 * from AnimalCrosswordPack project to make this demo work correctly.
 * 
 * @author mdoery
 *
 */
public class MainActivity extends Activity {
    private static String sep = System.getProperty("line.separator");
    // package name of AnimalCrosswordPack
    private static String pkg = "com.mdoery.tut.maincrossword.pack";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // We will write the results of our test to the TextView.
        setContentView(R.layout.activity_main);
        TextView tv = (TextView)this.findViewById(R.id.textView1);
        // Start of displayed content.
        String text = "BEGIN ...." + sep;
        try {
            Context ctx = createPackageContext(pkg,
                    Context.CONTEXT_IGNORE_SECURITY);
            // Add contents of file animal_theme.txt 
            // from AnimalCrosswordPack app.  
            text += getText("txt/animal_theme.txt", ctx) + sep;
            // Print out the directory listing 
            text += "DIRECTORY CONTENTS: " + getFileList(ctx) + sep;
        } catch (Exception e) {
            // If there is an exception, show it in the TextView
            text += sep + e + sep;
        }
        text += "END";
        tv.setText(text);
    }

    private String getText(String filename, Context ctx) {
        String text = sep + "FILE " + filename + " CONTENT = ";
           try {
               InputStream is = ctx.getAssets().open(filename);
               // We guarantee that the available method returns 
               // the total size of the asset not more than 2G.
               int size = is.available();
               // Read the entire asset into a local byte buffer.
               byte[] buffer = new byte[size];
               is.read(buffer);
               is.close();
               text += " " + size + " " + new String(buffer);
           } catch (Exception e) {
               text += "EXCEPTION " + e;
           }
           text += getFileList(ctx);
           return text + sep;
    }

    private String getFileList(Context ctx) {
        String text = sep + "FILE LIST = " + sep;
        try {
            String[] l = ctx.getAssets().list("txt");
            for (String s : l) {
                text += s + ", ";
            }
        } catch (Exception e) {
            text += sep + "EXCEPTION: " + e + sep;
        }
        return text + sep;
    }
}

Now build each of the projects, and export them as Android Applications (right-click on the project name in Navigator view, then choose Export > Export Android Application and follow the instructions). In the final step, install both apps on your Android device. You should see MainCrossword appear in your list of apps. Open it, and you should see the following output:

Tada! The data from AnimalCrosswordPack has been read by the MainCrossword app. You’ve successfully shared data from one app to another.

You can also verify that the AnimalCrosswordPack doesn’t show up as a launchable app. You can’t open it.