Recipe to see changes in repo in real time

February 27, 2016

Today, I was playing with Markdown in a README.md file for a repository and thought it’d be helpful if I could see my changes in real time (this was a junk repo I was going to delete, so I wasn’t worried about a bunch of commits). Just thought I’d blog this for my own future reference.

Create a script to commit changes and reload Safari…

update.sh

#!/bin/bash

# Commit changes
git add README.md
git commit -m "formatting"
git push

# Reload Safari
osascript -e 'tell first window of application "Safari" to do JavaScript "window.location.reload(true)" in current tab'

Install fswatch (Mac’s version of inotifywait), a tool to monitor for changes to a file or folder.

brew install fswatch

With the following command, any change to our file will get committed and the page in Safari will be reloaded.

fswatch -o README.md | xargs -n1 ./update.sh

jenv ftw!

December 16, 2015

At the recent OpenMRS Worldwide Summit #OMRS15, I was helping out with the Saptarshi’s introductory tutorial and realized that the OpenMRS Standalone requires Java 7, meaning that it fails to run on Java 8. Yikes! But my Mac runs Java 8. How do I get Java 7 on my laptop without making a mess of things?

jenv to the rescue! jenv provides an easy way to manage multiple Java versions. Not only can you easily switch between Java versions, but you can configure different folders to run specific versions of Java. So, for example, you can run Java 7 for the OpenMRS Standalone and run Java 8 everywhere else.

Step zero. Install Homebrew. I already had this installed. I used to use Macports to install utilities on my Mac, but it was fairly invasive. Homebrew installs most everything under your user account, so it doesn’t mess with Mac’s view of the world and rarely, if ever, requires the use of sudo.

Step one. Install jenv.

brew install jenv

Step two. Install Java 7.

Grab the latest version of Java 7 SDK from Oracle’s Java 7 archive. For me, this meant navigating to the  Java SE Development Kit 7u80 downloads, accepting the license agreement, and then downloading and installing from the dmg package for Mac OS X x64.

Step three. Tell jenv about Java 7.

jenv add /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/

If your version number differs, then your command may differ slightly. You can always navigate into /Library/Java/JavaVirtualMachines/ to find your installed version folders.

Done!

Now you can navigate into your OpenMRS Standalone folder, set the preferred Java version with a command like:

jenv local oracle64-1.7.0.80

and then execute the standalone with:

jenv exec java -jar openmrs-standalone.jar

Logout of Citrix XenApp with Shortcut Key

December 1, 2015

logoff propertiesI use Citrix XenApp to connect to a virtual desktop for hospital apps and, being a keyboard junkie, I prefer to have a hotkey to logout from the Citrix session on my Mac. I keep having to reinvent this, so I’m “microblogging” it for future me.

Right-click on the desktop and create a shortcut

From then on (until the desktop gets reset), pressing ⌘⌥L will logout of Citrix XenApp.

PowerPoint Best Practices

February 6, 2015

I’ve seen countless presentations.  The best ones use PowerPoint with lots of words.  The more words, the better.  Recently, the number of people trying to make only a few takeaway points during a talk or distracting us with clever images has increased.  I thought it was time to offer a concise example of best practices.  Fortunately, this can be done in a single PowerPoint slide.  Here it is:

These aren’t the devs you’re looking for

February 2, 2015

In the OpenMRS community, we often hear reference to “core devs.”  Who are these people?  What make them “core devs” anyway?

A little history...
OpenMRS started when Regenstrief Institute and Partners In Health (PIH) decided to collaborate while building systems for separate HIV-related projects (Regenstrief in Kenya, PIH in Rwanda).  Originally, Ben Wolfe and Darius Jazayeri were the primary developers for Regenstrief and PIH, respectively.  Both developers were primarily focused on coming up with a solution for their own organization, but collaborated on the same platform toward that end.  Several years later, Paul Biondich, leading Regenstrief’s Global Health Informatics team, arranged for Regenstrief to fund and sustain three developers (Daniel Kayiwa, Wyclif Luyima, and Rafał Korytkowski) to be covered full-time to focus on OpenMRS development.  Because Daniel, Wyclif, and Rafał are able to focus solely on OpenMRS development (other developers in the community were volunteering time or being paid to work on OpenMRS part-time), these three devs have often been referred to as “core devs.”

OpenMRS is used all over the world with more than 115,000 downloads across more than 200 countries.  As of February 2015, there are 1263 subscriptions to the OpenMRS Developers Mailing List.  The initial release of OpenMRS 1.9 was thanks to substantive contributions from more than 70 devs.  GitHub shows 930 forks and over 140 contributors to OpenMRS Core for openmrs-core.  As of the OpenMRS Implementers Meeting in Maputo (#MOZ15), we introduced Developer Stages, both to recognize & empower developers based on their level of expertise and community engagement and to adopt a more scalable approach.  If you listen to Yehuda Katz’s great discussion Indie OSS, it’s not healthy to make a distinction between “the core team” and community.  So, why are we referring to a few developers as “core devs” and labeling the 99.9% as non-core?

These aren’t the devs you’re looking for.Obi-Wan Kenobi (sort of)

As a community, we need to evolve beyond using distinctions like “core” vs. “non-core” developers toward a more scalable approach: Developer Stages.  So, if you see me (or anyone in the OpenMRS community who agrees with me), calling someone out in the future for using the term “core devs,” please understand it’s not for a lack of respect & appreciation for the awesome contributions from those who have been attributed as such; rather, it’s out of an appreciation for all the awesome developers around the world in past & future, including the “core devs,” who have contributed and will contribute to saving lives through coding for OpenMRS.

From now on when you feel the urge to say “core devs,” try substituting it with “/dev/5’s” or “available /dev/4’s & /dev/5’s.”  As a community, we will be working to make this attribution easier to see and understand, so, in the future, when someone refers to the “available /dev/5’s”, they’ll be referring to far more than a three people. 🙂

 

Informal Feedback in a Single Click

September 26, 2014

There are many ways to capture feedback from users & testers, from feedback buttons built into the app to issue trackers and tools like JIRA Capture. Another method we have been using, especially for upcoming releases or new features or widgets, within the OpenMRS community is a side-by-side feedback page.

feedback-page

The application is on the left and an etherpad on the right. While I am not suggesting this as an approach for issue tracking, but we have found it to be a quick & easy way of collecting community feedback.  The combination of a link taking them directly to the product to be tested along with the near-zero activation energy required by etherpad makes it a handy combination.  It’s also nice to be able to throw a brief intro into the etherpad to direct people on what to test and how to report feedback.  And lastly, there’s a nice side effect of people seeing each other’s activity in real time.  When combined with a developer responding to feedback and re-deploying fixes in real time, it can be incredibly powerful.

Anyway, the main reason I decided to blog on this is because I tweaked our side-by-side tool a bit and wanted to throw my one-page feedback HTML in here for the next time I need it.  Here it is:



Feedback




OpenMRS Data Model Browser

June 27, 2014

Ever since the beginnings of OpenMRS, we’ve used the data model as a reference and as a teaching tool.  As the number of tables has grown, it has become harder to keep the data model diagram updated.  I also wanted an easy way to search for tables, columns, or foreign keys.  So, I created dbtohtml to generate an easily browsable, standalone, singe-page HTML view of the data model.

@should do behavior-driven testing

May 5, 2014

In 2008, when OpenMRS was struggling to adopt better test-driven development practices, I was lucky enough to read Dan North’s Introducing BDD.  As Dan says:

It suddenly occurred to me that people’s misunderstandings about TDD almost always came back to the word “test”.Dan North

How true!  For example, it’s common to see something like this when you start creating unit tests:

public class PatientTest {
  public void testPatient() {
    // test stuff here
  }
}

The next question is, what gets tested in a method called “testPatient”? I suppose the only wrong answer is “nothing.” But the problem is there are an infinite number of right answers… because “testPatient” doesn’t say anything about the behavior. As Dan points out, simply replacing the word “test” with the word “should” is a game changer. Let’s try again, except this time we will use “should” in our method name:

public class PatientTest {
  public void addIdentifier_shouldNotAddIdentifierThatIsInListAlready() {
    // make sure an identifier isn't duplicated
  }
}

It’s much easier to guess what will be tested inside that unit test’s method. That’s good… but it gets better. Dan’s suggestion of “should” not only places the focus on behavior, it also automagically forces testing to be scoped to a specific behavior, since any developer who sees a method name wrapping onto its third line instantly knows she is going about testing the wrong way and will look for help. Dan gives a great justification for this approach… but he had me at should.

Given Dan’s insight into using “should” instead of “test” to drive BDD, the trick was figuring out how we could engrain this approach within the OpenMRS community.  After some discussion, we came up with an idea that I’m still proud of today and I believe has helped us adopt a better testing culture.  Here’s what we did…

@should Javadoc tags

Testing is often filled with cookie-cutter code and requires additional effort that is difficult to sustain.  We wanted to find a way to overcome both of these challenges.  What we needed was a trivially easy way to generate behavior-focused tests.  So, we invented the @should Javadoc tag to allow developers to describe expected behaviors within the Javadoc and then we paid someone to develop an IDE plugin to auto-generate the test methods from existing method names.

Now that we have the @should tag, let’s take on more stab at testing.  Imagine you are writing some code for the Patient object…

public class Patient {
  public void addIdentifier(PatientIdentifier patientIdentifier) {
    // ...
  }
}

You know that an identifier shouldn’t be added twice for the same patient, so you simply state that behavior in the Javadoc:

public class Patient {
  /**
   * @should not add identifier that is in list already
   */
  public void addIdentifier(PatientIdentifier patientIdentifier) {
    // ...
  }
}

That’s it.  You’re already doing BDD!  Now, you tell your IDE to generate any missing unit tests for Patient and it automatically generates this method stub for you in the appropriate location:

public class PatientTest {
  public void addIdentifier_shouldNotAddIdentifierThatIsInListAlready) {
    // write your test here
  }
}

The IDE plugin automatically derives the proper location and method name from your @should tag and the associated method.  Now you can focus on testing that specific behavior without having to worry about any cookie-cutter code and adopting BDD is as simple as writing a Javadoc comment.

Benefits of using the @should Javadoc tag

Final Thoughts

We still have a long way to go down the road to full BDD, but I was very happy with our first step.  Over the years, the @should tag has become a handy tool for establishing a behavior-driven culture of testing; in fact, it has helped us adopt testing in general.  For any Java-shop that is wondering “How do we get our developers to start testing their code?”, I would strongly encourage you to read Dan North’s writings and consider adopting the @should Javadoc tag.

Related Resources

Freezing and Thawing Droplets in a DigitalOcean

April 21, 2014

DigitalOcean has been a game-changer for me.  Why create another space-hungry VM locally when you can spin up a new machine in 60 seconds on DO?  And it gets better: Tugboat.  Now, I can manage my droplets from the command line.  Since, I typically use (and reuse) droplets like I did local VMs, I often want to set a droplet aside for a while (maybe weeks or months) and return to it later.  Fortunately, DO provides a way to put snapshots into cold storage and then retrieve them later.  But freezing and thawing droplets wasn’t easy enough.  I made a suggestion to DO, but I doubt it’ll be implemented anytime soon (if ever), so I used Tugboat and some Groovy scripts to roll my own.

Here is what I was looking for:

Freezing a droplet

Thawing a droplet

The goal:

$ # Assume we have a droplet foo
$ tugboat create foo
$ # Imagine you're done working with foo for now
$ freeze foo
$ # Foo is a snapshot & the droplet is destroyed.
$ # ... weeks pass and you have a hankering for foo ...
$ thaw foo
$ # Foo is back, Baby!

While it would be great to have freeze & thaw button on the DO website freeze & thaw parameters for Tugboat, I didn’t have the time to make a pull request for Tugboat… so here are the scripts:

~/bin/freeze

This script will snapshot a droplet, replacing any snapshot of the same name, and destroy the droplet.

#!/usr/bin/env groovy

class TugboatException {
	// Our very own little exception is born. It's a buoy!
}

def getImageInfo = {
	"tugboat images".execute().text.split("\n").find{
		it.startsWith(imageName+" ")
	}
}

def waitFor = { imageName, to='appear' -> /* to='appear' or 'disappear' */
	attempts = 0
	while (true) {
		attempts++
		imageInfo = "tugboat images".execute().text.split("\n").find{
			it.startsWith(imageName+" ")
		}
		if ((imageInfo && to=='appear') || (!imageInfo && to=='disappear')) break
		if (imageInfo || attempts > 20) {
			throw new TugboatException("$imageName did not $to within 3 min. Gave up.")
		}
		sleep(10000) // wait 10 seconds between checks
	}
}

def cmd = { description, command ->
	print description
	response = command.execute().text
	println "done."
}

def cli = new CliBuilder(usage:'freeze DROPLET_NAME')
cli.q(longOpt:'quiet', '')
def options = cli.parse(args)

if (!options.arguments() || options.arguments().size != 1) {
	cli.usage()
	System.exit(0)
}

imageName = options.arguments()[0]

if (! "tugboat droplets".execute().text ==~ /(?ims).*^$imageName\s.*/) {
	println "$imageName droplet does not exist"
	System.exit(1)
}

imageInfo = getImageInfo()
if (imageInfo) {
	imageId = (imageInfo =~ /id:\s*(\d+)/)[0][1]
	if (imageId) {
		cmd("Destroying old $imageName image...", "tugboat destroy-image -c -i $imageId")
		waitFor(imageName, 'disappear')
	}
}

cmd('Telling droplet to halt...', "tugboat halt $imageName")

cmd('Waiting for droplet to shut down...', "tugboat wait $imageName -s off")

sleep(3000)

cmd('Taking snapshot of droplet...', "tugboat snapshot $imageName $imageName")

print "Waiting for image to complete..."
waitFor(imageName)
println "done."

cmd("Destroying $imageName droplet...", "tugboat destroy -c $imageName")

~/bin/thaw

This script will restore a frozen droplet and start it up for you.

#!/usr/bin/env groovy

def getImageInfo = {
	"tugboat images".execute().text.split("\n").find{
		it.startsWith(imageName+" ")
	}
}

def cmd = { description, command ->
	print description
	response = command.execute().text
	println "done."
}

def cli = new CliBuilder(usage:'thaw IMAGE_NAME')
cli.q(longOpt:'quiet', '')
def options = cli.parse(args)

if (!options.arguments() || options.arguments().size != 1) {
	cli.usage()
	System.exit(0)
}

imageName = options.arguments()[0]

if ("tugboat droplets".execute().text ==~ /(?ims).*^$imageName\s.*/) {
	println "$imageName droplet already exists"
	System.exit(1)
}

imageInfo = getImageInfo()
if (!imageInfo) {
	println "Image $imageName not found"
	System.exit(2)
}

imageId = (imageInfo =~ /id:\s*(\d+)/)[0][1]
if (!imageId) {
	println "Unable to parse $imageName image id"
	System.exit(3)
}

print "Thawing image..."
response = "tugboat create $imageName -i $imageId".execute().text
println "done."

print "Waiting for droplet to start..."
response = "tugboat wait $imageName".execute().text
println "done."

Guns make a difference

April 4, 2014

Guns make a difference

There are now two definitions for insanity:

in·san·i·ty [in-san-i-tee]
  1. Doing the same thing over and over again and expecting different results.
  2. Believing  the solution to gun violence is more guns.