Fix Home Needs

  • Subscribe to our RSS feed.
  • Twitter
  • StumbleUpon
  • Reddit
  • Facebook
  • Digg

Monday, 30 September 2013

Behavior-driven testing in Go with GoConvey (BDD in "golang")

Posted on 21:51 by Unknown

First: the built-in Go testing tools

Few things bring sweeter peace to the soul than making changes to Go code, then:

$ go test
...
PASS
ok

Go's built-in testing tools are good, but their structure doesn't easily allow for us to convey behavior. When we make an assertion, we assert that the opposite is not true instead of asserting that what we want is true:

if (a != b) {
    t.Errorf("Expected '%d' but got '%d'", a, b)
}

This has been very nice, but as projects get bigger, tests get more numerous, and structuring them so they are readable, avoid repetition, and clearly test specific behavior rather than just results becomes difficult to do idiomatically. (Plus, Go's default test output is hard to read.)

Enter GoConvey.

GoConvey is a library for behavior-driven development. It implements the same go test interface you're used to, and plays nicely in both default and verbose (go test -v) modes. GoConvey structures your tests in a way that makes them more readable and reliable. GoConvey has easy-to-read output that even uses colors (and if you're on Mac, Unicode easter eggs) for fast comprehension.

Finally: Your first GoConvey tests

Simply install GoConvey with:

$ go get github.com/smartystreets/goconvey

Then open your new test file. Be sure to name it something ending with "_test.go". It should look familiar to start (I'll be recreating this example file):

package examples

import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)

func TestSpec(t *testing.T) {
}

Notice that we import the "convey" package from GoConvey with the dot notation. This is an acceptable practice for our purposes, as you will see, since this is all for testing your production code and none of it actually gets built into your executable.

To start testing, let's begin to fill out that TestSpec func:

func TestSpec(t *testing.T) {
Convey("Subject: Increment and decrement", t, nil)
}

This sets up our first test harness. We pass in a string which acts merely as a comment, then the testing.T object (but only our top-level Convey() call should have it! -- you'll see in a minute), then nil. For now, this means "nothing to see here, move along" -- and that test will be skipped. In order to be useful, we must replace nil with a func():

func TestSpec(t *testing.T) {
Convey("Subject: Increment and decrement", t, func() {
var x int

Convey("When incremented", func() {
x++
})
})
}

As you can see, any nested calls to Convey() shouldn't have "t" passed in.

Within your functions, you can set up your test at the appropriate scope. Above, we've defined a function to test the subject ("Increment and decrement") and given it an int to work with (x). You may want to avoid using := notation at higher scopes until you're done nesting Conveys. (Figuring out why is left as an exercise for the reader.)

Our second level of Convey, then, tests various paths or situations that the subject may encounter. The harness we've specified tests the paths when x is incremented. Now it's time to make assertions. We'll make two:

func TestSpec(t *testing.T) {
Convey("Subject: Integer incrementation and decrementation", t, func() {
var x int

Convey("Given a starting integer value", func() {
x = 42

Convey("When incremented", func() {
x++

Convey("The value should be greater by one", func() {
So(x, ShouldEqual, 43)
})
Convey("The value should NOT be what it used to be", func() {
So(x, ShouldNotEqual, 42)
})
})
})
})
}

The nested structure is incredibly helpful as projects grow.

Feel free to make several assertions in a row, within one convey, or in a loop. Check marks will be placed at the end of the verbose output to indicate that each one has passed (or an X if it didn't pass).

Oh -- did I mention that you can now run your tests by doing:

$ go test

I usually prefer verbose mode:

$ go test -v

And if you want your tests to run automatically when you save your test files:

$ python $GOPATH/src/github.com/smartystreets/goconvey/scripts/idle.py

Similarly, if you want verbose mode, tack a -v on to the end of that.

Available functions / assertions

This primer ends here, but that should get you started with BDD in Go. Be sure to check out the README for another once-over, and the Godoc documentation on the assertions and methods you can use, since I didn't cover most of them here. Also see the examples folder for even more that aren't yet documented, such as a Reset() function and similar things.

GoConvey is a new library but has lots of promise for writing more robust and test-documented code. As you encounter certain needs that aren't yet met by the library, open an issue or fork it and contribute.
Read More
Posted in bdd, development, go, golang, testing, unit tests | No comments

Friday, 6 September 2013

How to set up dynamic DNS in 5 minutes

Posted on 15:37 by Unknown
With a dynamic DNS service (don't worry, there are free ones), you can use your domain name to point it to your home server, even if your home IP address changes sometimes. That way you can type yourdomain.com in your browser and connect to your home network, or any other machine you configure.

All the guides I found for this simple task were actually really hard and required installing and configuring obscure/old software. I'll show you how to do it without installing anything and you can be running in about 5 minutes.

Of course, I assume you're on a Mac or Linux computer. If you're running Windows, you'll probably have to install something. Also, I used Namecheap's free, built-in dynamic DNS service, and it's the only one I've tried so far.

I bet you can get this done in 2 easy steps!

  1. Enable dynamic DNS (I'll show you how with Namecheap)
  2. Set up automatic updates so your domain points to your home (or whatever)

1. Enable dynamic DNS

A dynamic DNS service is one which has access to modify your domain name and update the host (IP address) it points to.

I mainly use Namecheap for my domain registrations. Conveniently, they also have a free dynamic DNS service. (There are plenty of paid ones out there, but you may not need their features. I don't.) What follows is how I do this on Namecheap, but any registrar with a similar service probably works similarly.

Click on the domain name in your Namecheap account and make sure the domain name's nameservers are pointed to Namecheap. Then click "All Host Records" in the menu. I want my entire domain to point to my home server, so I set @ and www like so:

Your host records might look something like this
You may choose to use only a subdomain, in which case you'd just fill out a new row below in a similar fashion with the sub-domain (host name) of your choice.

Either way, the IP address you enter isn't too important right now. I just chose 127.0.0.1.

Save those settings, then click "Dynamic DNS" near the bottom of the menu:

Namecheap's built-in dynamic DNS works just fine
Make sure the "enable" option is checked and click the button to actually enable dynamic DNS.

Almost done! Keep that tab open, because we'll need the password given on that page.

2. Set up automatic updates

Now we just need to have "E.T. phone home" occasionally, so to speak. This means your home server will call out to your dynamic DNS provider and say "I'm here!" and give it your current IP address.

Most guides will tell you how to install and configure a dynamic DNS client -- but we're just going to use cURL and a cron job for this. (Okay, so if you don't have cURL already installed for some reason, then install it.)

If you're on Mac or Linux, this is easy enough with crontab. All you have to do is make a web request every so often. For example, you might add this to your crontab, if you're use Namecheap:

@hourly curl "https://dynamicdns.park-your-domain.com/update?host=HOST_NAME_HERE&domain=YOUR_DOMAIN_HERE&password=YOUR_PASSWORD_HERE&ip=`curl -s echoip.com`"

You can choose how frequently to have it run. Now let me explain the URL which you need to customize:
  • HOST_NAME_HERE - This would be the host name from the "All Host Records" page. If you're using the whole domain name (yourdomain.com), use @. If you're using the www prefix (www.yourdomain.com), then add a cron job that's exactly the same except with the host of www. Otherwise, this value would be the sub-domain you chose to use and made the A record for.
  • YOUR_DOMAIN_HERE - This is your actual domain name, e.g.: yourdomain.com
  • YOUR_PASSWORD_HERE - This is the password that Namecheap gives you on their "Dynamic DNS" page. 
The last parameter actually specifies the IP address to use. This little trick gets your public IP address using echoip.com.

It's important to note that anyone who has your password can set any host name on your domain with an A (Address) record to any IP address they want, effectively hijacking your domain name.

If you use another dynamic DNS provider or registrar, I'm not sure what the request URL would be for you. That's something you'd have to find out from the organization.

So there you go! Give it a few minutes (maybe a few hours) to propagate, and then your domain will be pointing to your own server.
Read More
Posted in development, dns, domain name, ip, ipaddress | No comments

Wednesday, 14 August 2013

How to customize your terminal prompt (with colors)

Posted on 12:05 by Unknown
At work, the Go projects we're working on have workspaces that get pretty deep and one of the directory names is repeated down the tree. I ended up having no room to type commands in my terminal that didn't wrap to the next line. Annoying!

I could have just limited the prompt to show me the name of the current directory and use pwd when needed, but that was kind of a pain. Then I watched this talk and saw his terminal, and got inspired.

So here's my terminal and prompt now:

Customized terminal prompt -- the red "root" text blinks as a warning/reminder
Doing this took a while to figure out (especially the colors, and getting the root user to share the look). It's a simple idea: edit your ~/.bash_profile file and specify the PS1 variable.

Here's how I did it (this works in both my Mac and Raspberry Pi):

Decoration1="\[\e[90m\]╔["
RegularUserPart="\[\e[36m\]\u"
RootUserPart="\[\e[31;5m\]\u\[\e[m\]"
Between="\[\e[90m\]@"
HostPart="\[\e[32m\]\h:"
PathPart="\[\e[93;1m\]\w"
Decoration2="\[\e[90m\]]\n╚>\[\e[m\]"
case `id -u` in
    0) export PS1="$Decoration1$RootUserPart$Between$HostPart$PathPart$Decoration2# ";;
    *) export PS1="$Decoration1$RegularUserPart$Between$HostPart$PathPart$Decoration2$ ";;
esac

Each segment that looks like \[\e[90m\] changes the color and attributes of the following text, according to an enumeration like this one. The following text keeps those settings until you change it again or reset it. The number before the m is the color. Sometimes there's a number, semicolon, and another number. The second number in that case specifies underline, bold, blink, etc. You can omit both entirely to reset the text to its default or previous style.

To get the root user to share the style, you can, if on a Mac, put that code in your /etc/bashrc file, or on any Linux system (including RPi), you can have the root user's .bashrc source your own .bashrc file. A simple line similar to the following should do the trick (include the dot at the beginning of the line; or you can replace it with source):

. /home/pi/.bashrc

You may also like to have a blank line before each prompt to make it feel a little more "roomy" and less cluttered. You can easily do this by adding \n at the beginning of the $Decoration1 value.

I've really enjoyed this new prompt style. I have plenty of room to type, can clearly see my user, hostname, and current path at a glance. My prompts don't get lost in lots of output (like a disastrous g++ compile).

You're welcome to use it and customize it however you'd like. I'd be interested to see what you come up with.
Read More
Posted in command line, linux, mac, osx, raspberry pi, terminal | No comments

Friday, 9 August 2013

Automatically make a Raspberry Pi with wifi support

Posted on 21:59 by Unknown
Okay, I love my Raspberry Pi, but setting it up just the way I want got so involved I was afraid I couldn't do it again if I had to. So I wrote a script to automate it, and decided to publish it.

So stop baking your Pi manually. MakeMyPi automates the process. It even configures the WiFi for you (assuming you're using a dongle that's supported by the OS, Raspbian Wheezy).

Visit the project on GitHub

or

View an asciicast of the script in action
MakeMyPi in action

(Sorry, but right now, it only works from a Mac.)

All you have to do is:
  1. Make sure your own public key is in ~/.ssh/id_rsa.pub (which is the default location)
  2. Follow the easy configuration instructions
  3. Run the script and follow instructions; it will ding at you when it needs your attention
MakeMyPi can do the following for your Raspberry Pi:
  • Download an operating system image for you, if necessary
  • Write the operating system image file to the SD card
  • Install its own public/private key pair that you provide it
  • Authorize your own public key to log into it
  • Configure network & wifi (assuming you use supported hardware)
  • Create useful aliases
  • Install helpful and necessary packages
  • Run your own custom provisioning script
Basically, it takes the pain away. No more forgetting how to do certain foundational things. In about 5 minutes, I have a working Raspberry Pi, configured just the way I want, on a brand new SD card.

Have fun. Contribute. Go crazy. Enjoy your pi.
Read More
Posted in cli, command line, linux, mac, raspberry pi, terminal | No comments

Wednesday, 31 July 2013

Easily get your external IP address using the terminal or command line

Posted on 19:42 by Unknown
I want my little Raspberry Pi to "phone home" once in a while so I know where it's at. I wanted a very simple web service that simply regurgitated the requester's IP address in plain text. Nothing more, nothing less.

About to write it myself, I typed in echoip.com and found exactly what I wanted. It's been a good day!

To get your external IP address from the command line or Linux terminal:

$ curl echoip.com

To save it to a file:

$ curl -s echoip.com > myip

Or, if you prefer wget:

$ wget echoip.com --no-cache -qO-

To get super-nerdy and make your life even easier:

$ alias echoip="curl echoip.com"
$ echoip

(You could use any of the variations above for your alias' command.)

In the end, this is the command I added to my Raspberry Pi's crontab (I've changed sensitive parts):

$ curl -s echoip.com | ssh user@mysite.com "cat > /www/mysite/ipaddress.txt"

As a side note, if you want JSON output, there's jsonip.com, or for XML, there's xmlip.com (which actually does XML, JSON, and plain-text, but I feel like the server is a bit slower -- and the homepage doesn't actually serve XML. *facepalm*)

I don't know who made echoip.com, but props to them for choosing the same name I would have. (For some reason, I thought that the output at plainip.com would be... well, plainer.) Memo to self: if echoip ever goes offline, write my own to replace it.
Read More
Posted in command line, ip, ip address, linux, raspberry pi, terminal | No comments

Tuesday, 30 July 2013

Using Vagrant and cross-compiling Go (golang)

Posted on 11:21 by Unknown
This is mostly a memo-to-self about how to write Go code in my Mac environment, compile it there for a Linux environment, and run it in a production-like Linux environment using Vagrant.

If using Homebrew...

My Go installation was home-brewed. If you used Homebrew to install Go, you need to make sure you did with the --cross-compile-common flag. If not, no problem, just run these:

$ brew update
$ brew upgrade go --cross-compile-common

Or do --cross-compile-all for all supported platforms.

If not using Homebrew...

If you're not using a Homebrew install of Go, you need to make Go support cross-compilation yourself. First you just need to tell Go to put the package files for the other architecture(s) in its pkg directory.

$ cd /usr/local/go/src
$ sudo GOOS=linux GOARCH=amd64 CGO_ENABLED=0 ./make.bash --no-clean

For GOOS, you can specify linux, darwin (for Mac), windows, and freebsd. For GOARCH, you can specify amd64, 386 (for 32-bit), and arm. As of Go 1.1, cgo is disabled by default, so the CGO_ENABLED part isn't necessary if you're setting it to 0, but to enable it you'll need to set it to 1.

Cross-compile your Go program

Once the make is finished, you can change into your Go program's directory and build or install like this:

$ GOOS=linux GOARCH=amd64 go build -o myprogram.linux64 prog.go

myprogram.linux is what I'm calling the output file in this case to remind me that the binary is for 64-bit Linux systems. For Windows, you'd want to call it something ending in .exe. Finally, prog.go is the name of my source file.

You can also do an install instead of just a build using the same technique.

Set it up with Vagrant

Coming soon.
Read More
Posted in compile, cross-compile, go, golang, install, linux, mac, vagrant | No comments

Monday, 15 July 2013

Why yes, Go/Golang, I still want to read my CSV file!

Posted on 21:46 by Unknown


UPDATE: This appears to have been fixed and the fix ships with Go 1.2.




I like Go (1.1.1), but how disappointing that in order to read this:

1,O1,,,,0.0000,0.0000,,
2,AP,,,,35.0000,105.0000,,
3,EU,,,,47.0000,8.0000,,
4,AD,,,,42.5000,1.5000,,

I need to tell this code:

csvFile, err := os.Open("/path/myfile.csv")
defer csvFile.Close()
if err != nil {
    panic(err)
}
csvReader := csv.NewReader(csvFile)
for {
    fields, err := csvReader.Read()
    if err == io.EOF {
        break
    } else if err != nil {
        panic(err)
    }
    // ... do stuff ...
}

to allow the last field in any row to be empty:

csvReader.TrailingComma = true

Otherwise:

panic: line 1, column 22: extra delimiter at end of line

Really??

One word: BIZARRE.

Even though my dissatisfaction with Go is quite low, this is probably my biggest disappointment so far. It's an obscure, barely-documented bug feature in the encoding/csv package that will have you scratching your head because your CSV file is perfectly formatted. It's all thanks to this fine logic:
// We don't allow trailing commas.
So by trailing comma, they really mean, empty last fields. Um. Why.

Doesn't Go REQUIRE trailing commas when defining map and struct literals? Why forbid them here? Kind of odd considering it's an engineered language.

A line break or comma in the data would enclose the field in quotes... so a comma at the end of a line must mean that the last field is... empty. That is all.

O little csv.Reader, your name is Reader, not SyntaxChecker. Leave it to the file's author to fix those flaws. But please, just read this properly-formatted CSV file in all its little glory. That's all I ask of you, csv.Reader... that's all I ask...
Read More
Posted in csv, go, golang | No comments
Newer Posts Older Posts Home
Subscribe to: Posts (Atom)

Popular Posts

  • How to take FrontRunner from Provo to SLC airport
    I see this question a lot: how do I get from Provo or Orem to the SLC International Airport entirely by train (UTA FrontRunner/Trax)? Here I...
  • Behavior-driven testing in Go with GoConvey (BDD in "golang")
    First: the built-in Go testing tools Few things bring sweeter peace to the soul than making changes to Go code, then: $ go test ... PASS ok ...
  • Fix the Home and End keys on Mac OS X (Mountain Lion)
    If you use a keyboard that's not designed specifically for Macs, you probably are familiar with the annoying mapping of the Home and End...
  • Why yes, Go/Golang, I still want to read my CSV file!
    UPDATE: This appears to have been fixed and the fix  ships with   Go 1.2 . I like Go (1.1.1), but how disappointing that in order to read ...
  • Installing nginx / PHP / MySQL on Mac OS X Mountain Lion
    ** Update: See a quicker way to do this using Homebrew (this method uses Macports, and it's considerably more difficult). ** ... are yo...
  • Using Vagrant and cross-compiling Go (golang)
    This is mostly a memo-to-self about how to write Go code in my Mac environment, compile it there for a Linux environment, and run it in a pr...
  • Automatically make a Raspberry Pi with wifi support
    Okay, I love my Raspberry Pi, but setting it up just the way I want got so involved I was afraid I couldn't do it again if I had to. So ...
  • Install nginx / PHP / MySQL on Mac OS X Mountain Lion with Homebrew
    Last time I wiped my Macbook Pro, I used Macports to install my web development environment . Doing it that way was really hard compared to ...
  • Printing to BYU campus printers without extra software (Mac OS X)
    Macs come with a print server installed by default. This means you don't need to install any drivers or software to print to the BYU ca...
  • External hard drive backups while you sleep
    On most modern computers, there's an energy saver preference which will shut down your hard disks when the computer is idle or in "...

Categories

  • backup
  • bdd
  • byu
  • chrome
  • cli
  • command line
  • commute
  • compile
  • cross-compile
  • csv
  • development
  • dns
  • domain name
  • fastcgi
  • fcgi
  • go
  • golang
  • homebrew
  • inkscape
  • install
  • ip
  • ip address
  • ipaddress
  • itunes
  • javascript
  • keybinding
  • linux
  • mac
  • mountain lion
  • mysql
  • nginx
  • optimization
  • osx
  • parsing
  • pecl
  • php
  • printing
  • raspberry pi
  • security
  • ssd
  • terminal
  • testing
  • transportation
  • unit tests
  • vagrant

Blog Archive

  • ▼  2013 (16)
    • ▼  November (1)
      • How to take FrontRunner from Provo to SLC airport
    • ►  October (1)
    • ►  September (2)
    • ►  August (2)
    • ►  July (3)
    • ►  June (1)
    • ►  May (1)
    • ►  April (2)
    • ►  March (2)
    • ►  February (1)
  • ►  2012 (8)
    • ►  November (1)
    • ►  October (1)
    • ►  September (6)
Powered by Blogger.

About Me

Unknown
View my complete profile