<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>#! jetmore</title>
	<atom:link href="http://www.jetmore.org/john/blog/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.jetmore.org/john/blog</link>
	<description></description>
	<lastBuildDate>Fri, 11 May 2012 19:16:45 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>Adding An Icon To The &#8220;Edit With Sublime Text 2&#8243; Contextual Menu Item</title>
		<link>http://www.jetmore.org/john/blog/2012/05/adding-an-icon-to-the-edit-with-sublime-text-2-contextual-menu-item/</link>
		<comments>http://www.jetmore.org/john/blog/2012/05/adding-an-icon-to-the-edit-with-sublime-text-2-contextual-menu-item/#comments</comments>
		<pubDate>Fri, 11 May 2012 19:10:20 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[tweaks]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=650</guid>
		<description><![CDATA[As mentioned elsewhere I&#8217;ve totally fallen in love with Sublime Text 2 as an editor. One of the issues I&#8217;ve had switching to using it on Windows, though, is the muscle memory I&#8217;ve developed for selecting the &#8220;Edit With Notepad++&#8221; &#8230; <a href="http://www.jetmore.org/john/blog/2012/05/adding-an-icon-to-the-edit-with-sublime-text-2-contextual-menu-item/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>As <a href="http://www.jetmore.org/john/blog/2012/05/getting-up-to-speed-with-sublime-text-2-a-guide-for-notepad-users/">mentioned elsewhere</a> I&#8217;ve totally fallen in love with Sublime Text 2 as an editor.  One of the issues I&#8217;ve had switching to using it on Windows, though, is the muscle memory I&#8217;ve developed for selecting the &#8220;Edit With Notepad++&#8221; contextual menu option.  I think part of the reason I&#8217;m having trouble switching is that the Notepad++ item has an icon and, by default, the Sublime Text 2 item does not.  Several weeks ago I figured out how to add an icon to the Sublime Text 2 item and it has definitely reduced the number of times I&#8217;ve accidentally chosen Notepad++.  Better write it down before I forget how I did it&#8230;</p>
<p><span id="more-650"></span></p>
<p>The basics of what I did were taken from a <a href="http://www.sublimetext.com/forum/viewtopic.php?t=3159&#038;f=4">forum posting</a> which ultimately yielded the information I was looking for, but which presented commands I wasn&#8217;t prepared to run unvetted, and presented several different possible actions to take without being obvious which ones ended up being &#8220;correct&#8221;.</p>
<p>For full disclosure, the steps below worked for me on a Win7 x86_64 machine.</p>
<p>If I were to distill the action I took into a single command, ala the ones presented in the forum, it would be this:</p>
<pre class="brush: powershell; title: ; notranslate">
reg add &quot;HKEY_LOCAL_MACHINE\SOFTWARE\Classes\*\shell\Open with Sublime Text 2&quot; /t REG_EXPAND_SZ /v &quot;Icon&quot; /d &quot;C:\Program Files\Sublime Text 2\sublime_text.exe,0&quot;
</pre>
<p>In truth though I was too chicken to run that actual command without fully understanding what I was doing.  So here are the actual steps I took to add the icon:</p>
<ul>
<li>run `regedit` (windows-R -> &#8220;regedit&#8221; -> enter)</li>
<li>Find (ctrl-F) &#8220;Open with Sublime Text 2&#8243;.
<ul>
<li>On my machine there were two results, one in HKEY_CLASSES_ROOT and one in HKEY_LOCAL_MACHINE.  Based on the discussion in the forum post above I chose to use the one in HKEY_LOCAL_MACHINE</li>
</ul>
</li>
<li>Find the path to the executable being referenced by clicking on the &#8220;command&#8221; key under HKEY_LOCAL_MACHINE.  It&#8217;s the &#8220;Data&#8221; portion of the Default key, minus the %1.
<ul>
<li>On my machine it was &#8220;C:\Program Files\Sublime Text 2\sublime_text.exe&#8221;</li>
</ul>
</li>
<li>Add new key:
<ul>
<li>select &#8220;Open with Sublime Text 2&#8243;</li>
<li>Choose the Edit->New->Expandable String Value menu item</li>
<li>Change name of new entry to &#8220;Icon&#8221;</li>
<li>Right click new value, select modify</li>
<li>Enter the value you picked up from the command key into the &#8220;Value data&#8221; field, with &#8220;,0&#8243; added to the end
<ul>
<li>Example: &#8220;C:\Program Files\Sublime Text 2\sublime_text.exe,0&#8243;</li>
</ul>
</li>
<li>Click &#8220;OK&#8221;</li>
</ul>
</li>
<li>Exit regedit</li>
</ul>
<p>After those steps, you should have an Icon next to the &#8220;Edit with Sublime Text 2&#8243; contextual menu item, like so:</p>
<p><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/04/sublime-context-icon.png"><img src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/04/sublime-context-icon.png" alt="" title="sublime-context-icon" width="327" height="114" class="aligncenter size-full wp-image-584" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/05/adding-an-icon-to-the-edit-with-sublime-text-2-contextual-menu-item/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Getting Up To Speed With Sublime Text 2 &#8211; Notes For Notepad++ Users</title>
		<link>http://www.jetmore.org/john/blog/2012/05/getting-up-to-speed-with-sublime-text-2/</link>
		<comments>http://www.jetmore.org/john/blog/2012/05/getting-up-to-speed-with-sublime-text-2/#comments</comments>
		<pubDate>Fri, 11 May 2012 18:41:35 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[tweaks]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=577</guid>
		<description><![CDATA[I recently started a love affair with the Sublime Text 2 text editor. Looking around the web, I&#8217;m certainly not alone, and there are many great tips and tricks posts. I probably can&#8217;t write anything better than what&#8217;s already out &#8230; <a href="http://www.jetmore.org/john/blog/2012/05/getting-up-to-speed-with-sublime-text-2/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I recently started a love affair with the <a href="http://www.sublimetext.com/2">Sublime Text 2</a> text editor.  Looking around the web, I&#8217;m certainly not alone, and there are many great tips and tricks posts.  I probably can&#8217;t write anything better than what&#8217;s already out there, so this post is really just an attempt to document for my future self my own customization decisions and the logic behind my choices.</p>
<p><span id="more-577"></span></p>
<h1>I Need Another Text Editor Like I Need a Hole In The Head</h1>
<p>An important piece of context for this post:  I went looking for a new editor specifically because I was annoyed at switching between <a href="http://www.barebones.com/products/bbedit/">BBEdit</a> on Mac and <a href="http://notepad-plus-plus.org/">Notepad++</a> on Windows (I also use vi/vim/gvim on all platforms, but am not annoyed in the least with it).  If either BBEdit of Notepad++ were on both platforms, I would be using it and not considering ST2.  But they aren&#8217;t, and I am, and so much of this post is &#8220;how do I replicate this feature in Notepad++&#8221; or &#8220;I wish this cool BBEdit thing would work&#8221;.  There&#8217;s even the short term effect of turning off interesting features in ST2 because I found them distracting.  Long term I will revisit these choices, but in the short term my goal was to make ST2 as usable as BBEdit or Notepad++ as quickly as possible, even if that mean losing possible ST2 functionality.</p>
<h1>Pay The Man</h1>
<p>Sublime Text 2 is commercial software.  It&#8217;s free to use right now (it is in beta) but the author reserves the right to charge in the future.  After I used it for a while I didn&#8217;t need to wait.  This software is definitely worth the $59 I paid for it, and I hope it motivates the author to keep working on it.</p>
<h1>Settings</h1>
<p>I might as well start this by jumping straight into the user settings.  ST2 uses very readable JSON files to control things like preferences and keybindings.  Setting &#8220;preferences&#8221; is really just opening a JSON file in the editor itself and typing away.  All of the defaults are in a well-commented file, available from the &#8220;Settings &#8211; Default&#8221; menu option.  Here&#8217;s a snippet of the file contents:</p>
<pre class="brush: jscript; title: ; notranslate">
    // Set to true to insert spaces when tab is pressed
    &quot;translate_tabs_to_spaces&quot;: false,

    // If translate_tabs_to_spaces is true, use_tab_stops will make tab and
    // backspace insert/delete up to the next tabstop
    &quot;use_tab_stops&quot;: true,

    // Set to false to disable detection of tabs vs. spaces on load
    &quot;detect_indentation&quot;: true,

    // Calculates indentation automatically when pressing enter
    &quot;auto_indent&quot;: true,
</pre>
<p>Two great side effect of using a commented text file for configuration:  Finding an option is as easy as searching for a keyword, and the configuration is versionable.  The awesomeness of neither of these should be underestimated.  </p>
<p>Once you find a setting you want to tweak, open the user-specific settings file (&#8220;Settings &#8211; User&#8221;) and add them there.  Here is my current user-specific settings file, followed by some brief discussion of why I set the option:</p>
<pre class="brush: jscript; title: ; notranslate">
{
    &quot;auto_match_enabled&quot;: false,
    &quot;auto_complete&quot;: false,
    &quot;color_scheme&quot;: &quot;Packages/Color Scheme - Default/Mac Classic.tmTheme&quot;,
    &quot;draw_white_space&quot;: &quot;all&quot;,
    &quot;font_face&quot;: &quot;Courier New&quot;,
    &quot;trim_automatic_white_space&quot;: false,
}
</pre>
<ul>
<li><b>auto_match_enabled</b> &#8211; This is a great example of what is probably a powerful feature that my own habits made me turn off.  This option controls whether or not ST2 auto-closes braces and brackets for you.  I really liked the idea of this option, but in practice it was more trouble than it was worth.  It would auto-add the closing brace, and then I would either have to arrow/mouse past the auto-added closing brace, or forget that it had been added already and re-add it, which would cause me to go back and remove the extra closing brace.  If I were to reimplement this feature, I would make ST2 try to detect whether I had just added an extra closing brace.  If I had, ST2 should remove the auto-added brace.  In the short term, it&#8217;s just easier to turn this feature off entirely.</li>
<li><b>auto_complete</b> &#8211; While I had it turned on, auto_complete was pretty interesting.  The auto-complete of functions, language keywords, etc, was great.  The problem is that the little box that popped up for auto-completion options while you typed, even if you didn&#8217;t want to auto-complete, that I had to turn it off.  I think there are more tunables around this (for instance, increasing delay before completion options are presented, or requiring a manual keystroke to get completion options) but in the short term this option was actually making me less productive so I just turned it off.</li>
<li><b>color_scheme</b> &#8211; I wanted to like all the dark color schemes that ST2 had to choose from, but in the end I had to have a white background.  The &#8220;Mac Classic&#8221; theme was the best &#8220;white background&#8221; theme I could find (which probably means it&#8217;s the closest to what Notepad++ uses by default).</li>
<li><b>draw_white_space</b> &#8211; Visual white space was one of the first features on Notepad++ that I became completely addicted to and could not live without.  This option actually has three settings &#8211; &#8220;none&#8221;, &#8220;selection&#8221;, and &#8220;all&#8221;, with &#8220;selection&#8221; being the default.  &#8220;Selection&#8221; (in which whitespace is only made visual in selected text) is actually a really nice idea, but I had gotten addicted to having it turned on all the time.
<li><b>font_face</b> &#8211; When I first began using ST2 I had a hard time engaging with it.  One of the factors were the dark-by-default themes, which I changed fairly quickly, but there was a lingering discomfort too that took me a while to identify.  I eventually realized that I wasn&#8217;t comfortable with the default font.  ST2 ships with &#8220;Consolas&#8221; as the default font on Windows, and &#8220;Menlo Regular&#8221; as the default on Mac OS X.  Changing these both to &#8220;Courier New&#8221; made a huge difference in my comfort in making ST2 my default editor.
<li><b>trim_automatic_white_space</b> &#8211; Another feature I turned off.  ST2 has a fantastic auto-indenter (better than Notepad++&#8217;s in my opinion) but this feature worked against me.  Apparently I move the I-beam around a lot when I am working, and this feature caused the auto-added whitespace to be removed when I (very temporarily) moved the I-beam off the line, which was pretty awful.  I think one reason this was left on was that, technically, it results in &#8220;trailing white space&#8221; when you don&#8217;t type anything on a line.  I happen to see this as a feature when also using visual white space
</ul>
<h1>Sane Tab-Switching</h1>
<p>Another tweak I made was changing the default tab-switching behavior.  The default, stack-like, MRU-ish behavior was unnatural to me.  I changed it to the more standard &#8220;cycle in tab order&#8221; style.  This is done in the &#8220;Preferences -> Key Bindings &#8211; User&#8221; file, by adding these two lines:</p>
<pre class="brush: jscript; title: ; notranslate">
    { &quot;keys&quot;: [&quot;ctrl+tab&quot;], &quot;command&quot;: &quot;next_view&quot; },
    { &quot;keys&quot;: [&quot;ctrl+shift+tab&quot;], &quot;command&quot;: &quot;prev_view&quot; }
</pre>
<h1>Indenting</h1>
<p>I&#8217;m of mixed opinion on ST2&#8242;s indenting vs. Notepad++&#8217;s (they are both better than BBEdit).  Out of the box, I think Notepad++&#8217;s is better, but with some tweaking I&#8217;m starting to think that ST2 has pulled ahead.</p>
<h3>Understanding Alternate Indenting Styles</h3>
<p>ST2 is definitely better at following the indent of the previous line, rather than sticking slavishly to the indents set for the file.  Specifically, consider the case of a single &#8220;line&#8221; of code split across multiple lines.  In that case I &#8220;tab&#8221; to the natural indent, and then indent with spaces after that.  In Notepad++, on the second space-indented line, it will indent with tabs, because it &#8220;knows&#8221; that tabs are the indent character for that doc.  Sublime Text 2, on the other hand, will recognize that I&#8217;ve altered my indenting and will use the same indenting for following lines:</p>
<div id="attachment_643" class="wp-caption aligncenter" style="width: 400px"><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/05/indent-npp.png"><img src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/05/indent-npp.png" alt="" title="indent-npp" width="390" height="67" class="size-full wp-image-643" /></a><p class="wp-caption-text">Notepadd++ annoyingly ignores the previous line and uses tabs in lines 3</p></div>
<div id="attachment_644" class="wp-caption aligncenter" style="width: 386px"><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/05/indent-st2.png"><img src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/05/indent-st2.png" alt="" title="indent-st2" width="376" height="63" class="size-full wp-image-644" /></a><p class="wp-caption-text">Sublime Text 2 awesomely recognizes my indent style in line 2 and mimics it in line 3</p></div>
<h3>Reindenting</h3>
<p>One area I&#8217;m really torn over is &#8220;re-indenting&#8221;.  I suspect that Sublime Text 2 is technically better at it, but that it&#8217;s more effort to make it work.  I maintain a lot of legacy code has often been maintained by copying tabbed code out of a teminal and then pasted into a new file, resulting in 8-space indents, instead of the 4-space-tab indents we&#8217;ve agreed to use.  In Notepad++, reindenting that code is very easy &#8211; Change a tab to 8-spaces in the preferences, select the entire block you want to re-indent, then hit tab (which indents the entire block one additional level, but also changes the 8-spaces into tabs), then hit shift-tab (which removes the extra indent level you just added, but leaves you with tabs instead of spaces).  Then change you tabs back to 4-spaces, and you&#8217;re done.  (It sounds complex as I read that back to myself, but it&#8217;s a fairly fluid action.)</p>
<p>Sublime Text 2, on the other hand, doesn&#8217;t quite do this (by default).  It will block-indent/unindent of course, but it won&#8217;t automatically convert spaces to tabs.  I finally realized that there&#8217;s an actual menu option to do what I want (Edit -> Line -> Reindent), and it works reasonably well.  I haven&#8217;t figured out all of the nuances yet, but it is not 100% rock-solid as Notepad++&#8217;s functionality.  It is good enough to use though.  It lacks a default key binding, and I use it enough that I added one (Preferences -> Key Bindings &#8211; User) to bind ctrl+shift+r to Reindent:</p>
<pre class="brush: jscript; title: ; notranslate">
    { &quot;keys&quot;: [&quot;ctrl+shift+r&quot;], &quot;command&quot;: &quot;reindent&quot; }
</pre>
<h1>Awesome stuff</h1>
<p>While I first focused on getting ST2 modified to be as similar as possible to Notepad++ or BBEdit, there are (at least) a few things that have stood out as being awesome and unique to Sublime Text 2 (as far as I know).</p>
<h3>Scrolling Past &#8220;End Of File&#8221;</h3>
<p>The first one is really simple but amazingly useful.  When you scroll a file in ST2, you can scroll &#8220;past&#8221; the end of the file.  In other words, you can scroll all the way until the last line of the file is the only visible line in the document (contrasting with the standard practice of allowing you to scroll until the last line in the file is on the last (bottom) visbile line in the editor window).  This is shockingly useful, and not even abstractly &#8211; I&#8217;ve spent the last 15 years adding 20 lines of whitespace to the bottom of files as a poor-man&#8217;s version of this.  Having it as a native feature in the editor is wonderful, and a great example of something I never would have thought to ask for, because &#8220;that&#8217;s not how editors work&#8221;.</p>
<h3>&#8220;minimap&#8221;</h3>
<div id="attachment_646" class="wp-caption aligncenter" style="width: 439px"><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/05/minimap.png"><img src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/05/minimap.png" alt="" title="minimap" width="429" height="355" class="size-full wp-image-646" /></a><p class="wp-caption-text">The text for this post - how meta!</p></div>
<p>The minimap is another area that is phenomenal.  It&#8217;s like a visual scroll-bar, with sensitity set halfway between just scrolling the I-beam with the arrow keys and dragging the actual scrollbar.  It&#8217;s so good that I only just realized that the standard scrollbar doesn&#8217;t have arrows on the top and bottom &#8211; I&#8217;ve never needed to use them!</p>
<h3>Easy and Powerful Layout Switching</h3>
<p>Another feature I didn&#8217;t know I needed was the fast layout switching.  The ability to quickly change from one-up to two-up, vertical or horizontal layouts is really powerful.  Since there&#8217;s no cognitive overhead to changing the layout (View -> Layout -> (option)) or moving documents to different frames, I&#8217;m much more likely to use it to compare docs, or use another doc as a ref, which I&#8217;ve found has a noticeable performance benefit.</p>
<h3>Project and Folders</h3>
<p>One feature I didn&#8217;t really think I would use was projects, or adding folders to an editor window.  It turns out that I tried it an instantly benefitted from it.  I haven&#8217;t developed an ecosystem of &#8220;projects&#8221;, but I have found that I add and remove folders from my editor fairly regularly as I work.  Being able to have all my files quickly reachable from within the editor itself, even if I don&#8217;t have them open for editing, is another time saver.</p>
<h1>Open Issues</h1>
<p>I don&#8217;t have a ton of open issues, but there are a couple of areas I&#8217;m still trying to figure out.</p>
<h3>Unfocused Clicks on Minimap</h3>
<p>The first is an issue with clicking in the minimap.  When Sublime Text 2 is in focus, clicking in the minimap, but outside of the &#8220;this is what you&#8217;re currently looking at&#8221; grey box, will jump you to where you clicked.  This makes sense.  However, it does the exact same thing if the ST2 window does NOT have focus.  I&#8217;ve lost the link now but this was raised on the ST2 forums and the author flatly stated that it wass working as designed and he would not change it.  In my opinion this is a really poor design choice.  I often switch back and forth between an editor, a terminal, and an scp client, and I can do it quickly my keeping parts of the windows visible and clicking into them.  This works great until I click into ST2 and suddenly I&#8217;m in a completely different part of the file because the part that was visible to click was the minimap.  More than anything, it surprised me that the author was so completely against it, to the point of being unwilling to offer a switch for the behavior.  I&#8217;ve learned to live with it but I&#8217;m still puzzled at the choice.</p>
<h3>&#8220;Find All&#8221; Results</h3>
<p>In general I&#8217;ve found searching in ST2 to be as good as searching in Notepad++, possibly a little better.  However, one of the few areas I&#8217;ve found in which Notepad++ is inarguably better (to the point where I suspect maybe I just haven&#8217;t found the right interface in ST2) is in the &#8220;Find all in document&#8221;, &#8220;Find all in open files&#8221;, and &#8220;Find in files&#8221; interfaces.  It&#8217;s not that ST2 doesn&#8217;t have these options, it&#8217;s that the results are unusable.  In Notepad++, there is a tray that opens from the bottom of the window that show all the matches and (if applicable), which file they are from.  These results can be copied, they can be collapsed by file name, and double clicking the line takes you that that line in the appropriate file.</p>
<p>When searching in files, you can almost recreate the Notepad++ experience.  The results are presenting as in a new document.  Using a 2-up, row-based layout, you can have the results and the documents visible at the same time, and it appears that you can collapse files and double-click lines to go to the files.  However, this might be a case where flexibility actually hampers instead of helps.  I still much prefer the Notepad++ interface for this vs. the ST2 interface.  Additionally, that only really seems to be for &#8220;find in files&#8221;.  When doing &#8220;Find All&#8221; in just the active document, you end up with each match selected, but I haven&#8217;t found a good view of each of those matches.  I&#8217;m not sure if this it a shortcoming of ST2 or my ability to use it, but the area of finding multiple matches is one of the few areas I think Notepad++ is genuinely a better use experience than ST2.</p>
<h3>Split Screen Editing Of A Single Document</h3>
<p>One last item I would like to have is, surprisingly, a feature in BBEdit that I haven&#8217;t found in either Notepad++ or Sublime Text 2, and that&#8217;s split-editing a single document.  It&#8217;s can be wonderful to use one part of document as reference while editing a a different location in the same document.  I bet you could kludge something up for this in both NPP and ST2, but the ease of just grabbing the &#8220;splitter&#8221; on the scrollbar to make it happen is pretty wonderful, and something I would love to have in ST2.</p>
<h1>And For My Next Trick</h1>
<p>This post has existed as a draft for the last several weeks.  I knew it was time to finish it when I stopped noting &#8220;how to make ST2 as good as Notepad++&#8221; and started jotting down features I&#8217;d found that were entirely new.  So,I&#8217;ll let this post stand for now, a summary of changes I made to bring the features of Sublime Text 2 quickly up to par with those of Notepad++ and BBEdit that I was used to using daily.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/05/getting-up-to-speed-with-sublime-text-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Introducing Ippsy, The Experimental Javascript Application</title>
		<link>http://www.jetmore.org/john/blog/2012/05/introducing-ippsy-the-experimental-javascript-application/</link>
		<comments>http://www.jetmore.org/john/blog/2012/05/introducing-ippsy-the-experimental-javascript-application/#comments</comments>
		<pubDate>Thu, 10 May 2012 13:29:01 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Projects]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[ippsy]]></category>
		<category><![CDATA[oss]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=631</guid>
		<description><![CDATA[A year or so ago I spent a few hours writing an IPv4 subnet calculator in javascript. I didn&#8217;t have any real goal, just a desire to play with javascript. I promptly forgot about it. Then a month or so &#8230; <a href="http://www.jetmore.org/john/blog/2012/05/introducing-ippsy-the-experimental-javascript-application/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>A year or so ago I spent a few hours writing an IPv4 subnet calculator in javascript.  I didn&#8217;t have any real goal, just a desire to play with javascript.  I promptly forgot about it.  Then a month or so ago I found it again and dusted it back off.  I had a lot of fun rewriting it and then&#8230; forgot about it again.</p>
<p>After remembering it yet again last night, I decided enough was enough, filed the roughest edges off of it, and posted it:</p>
<p><strong><a href="http://www.ippsy.net/">Ippsy, The Experimental Javascript Application</a></strong></p>
<p><span id="more-631"></span></p>
<p>There&#8217;s not a lot of use to the internet at large in this tool (no one needs yet another IP Calculator), but the core of this app is written such that other screens/functions/pages/tools can be added to it.  At some point I&#8217;ll get a wild hair to add something new and this frame will be available to do it.</p>
<p>There are a couple of design rules that I think make this project interesting.  The first is that I intend it to never grow beyond a single HTML page with Javascript and CSS embedded directly in it.  This is not the greatest design choice for manageability, but my goal is that this tool is not a &#8220;website&#8221;, it&#8217;s an application that runs in a browser.  By making it a single HTML page app distribution is as simple as &#8220;save as&#8221;.</p>
<p>The second design choice, which is really driven by the first, is that I won&#8217;t use any external frameworks.  No jQuery, no ExtJS, etc.  I want to build this in relatively pure HTML/Javascript/CSS, while still making the site as pretty as possible (which it is not right now, I admit).</p>
<p>One specific goal I have not set it legacy browser version support.  There&#8217;s nothing in the tool right now that&#8217;s especially troublesome for older browsers, but I won&#8217;t much care if I introduce a problem in the future.  The app is very specifically targeted at &#8220;the browser I am using right now&#8221;, and more loosely targeted at &#8220;the last version or two of the bigger browsers&#8221;.</p>
<p>There, I published it.  Now I can forget about it again for another year and see if anyone finds it&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/05/introducing-ippsy-the-experimental-javascript-application/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TestLink Activity Summary</title>
		<link>http://www.jetmore.org/john/blog/2012/05/testlink-activity-summary/</link>
		<comments>http://www.jetmore.org/john/blog/2012/05/testlink-activity-summary/#comments</comments>
		<pubDate>Tue, 08 May 2012 17:32:31 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[oss]]></category>
		<category><![CDATA[testlink]]></category>
		<category><![CDATA[tweaks]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=617</guid>
		<description><![CDATA[It is very unlikely that I will be an admin for a TestLink install again for a while. This post is intended to put a period on my involvement in TestLink. The code I&#8217;ve published modifying or extending TestLink is &#8230; <a href="http://www.jetmore.org/john/blog/2012/05/testlink-activity-summary/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>It is very unlikely that I will be an admin for a TestLink install again for a while.  This post is intended to put a period on my involvement in TestLink.</p>
<p><span id="more-617"></span></p>
<p>The code I&#8217;ve published modifying or extending TestLink is now available in a <a href="https://github.com/jetmore/TestLink-Misc">github repository</a> to make it easier to download and play with.  The patches at github are not as granular as the diffs in the individual blog posts, but they are true patches and can be applied cleanly using the <code>patch</code> command.</p>
<p>The TestLink 1.9.3 API documentation I <a href="http://www.jetmore.org/john/blog/2012/02/testlink-1-9-3-api-documentation/">extracted via PHP doc</a> is available for <a href="http://jetmore.org/john/misc/phpdoc-testlink193-api/">live browsing</a> or for <a href="http://jetmore.org/john/misc/phpdoc-testlink193-api.tar.gz">downloading</a>.</p>
<p>Here are the individual posts I&#8217;ve made that attempt to extend or enhance TestLink:</p>
<ul>
<li><a href="http://www.jetmore.org/john/blog/2011/08/alternating-test-step-row-colors-in-testlink/">Alternating Test Step Row Colors in TestLink</a> &#8211; better visual separation for test steps</li>
<li><a href="http://www.jetmore.org/john/blog/2011/09/better-copypaste-when-editing-test-steps-in-testlink/">Better Copy/Paste When Editing Test Steps in TestLink</a> &#8211; stop TestLink&#8217;s awful habit of editing a step when you click on it</li>
<li><a href="http://www.jetmore.org/john/blog/2011/09/setting-default-font-in-testlink/">Setting Default Font in TestLink</a> &#8211; How to force steps&#8217; action and result text areas to be a monospace font by default</li>
<li><a href="http://www.jetmore.org/john/blog/2011/11/ui-improvements-for-testlinks-vertical-step-layout/">UI Improvements for TestLink’s Vertical Step Layout</a> &#8211; again with the alternating background color, but this time for the vertical layout</li>
<li><a href="http://www.jetmore.org/john/blog/2012/02/testlink-1-9-3-api-documentation/">TestLink 1.9.3 API Documentation</a></li>
<li><a href="http://www.jetmore.org/john/blog/2012/03/missing-testlink-api-function-getexecutionresults/">Missing TestLink API Function – getExecutionResults()</a></li>
<li><a href="http://www.jetmore.org/john/blog/2012/05/example-testlink-xmlrpc-clients-in-perl/">Example TestLink XMLRPC Clients in Perl</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/05/testlink-activity-summary/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Example TestLink XMLRPC Clients in Perl</title>
		<link>http://www.jetmore.org/john/blog/2012/05/example-testlink-xmlrpc-clients-in-perl/</link>
		<comments>http://www.jetmore.org/john/blog/2012/05/example-testlink-xmlrpc-clients-in-perl/#comments</comments>
		<pubDate>Tue, 08 May 2012 14:54:37 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[oss]]></category>
		<category><![CDATA[testlink]]></category>
		<category><![CDATA[tweaks]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=597</guid>
		<description><![CDATA[There are not many good examples floating around of Perl clients for TestLink&#8217;s XMLRPC API. There are two examples included with the 1.9.3 source, but they are very brief. I wrote two scripts to support a first-blush attempt at automated &#8230; <a href="http://www.jetmore.org/john/blog/2012/05/example-testlink-xmlrpc-clients-in-perl/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>There are not many good examples floating around of Perl clients for TestLink&#8217;s XMLRPC API.  There are two examples included with the 1.9.3 source, but they are very brief.  I wrote two scripts to support a first-blush attempt at automated test running, and while they will get a lot more refinement over time, I thought abstracted versions of them might be beneficial for others trying to write Perl clients (or in any language &#8211; they perl samples were so sparse I largely based my script on the python example).</p>
<p><span id="more-597"></span></p>
<h2>Test Runner</h2>
<p>The first script is an example test runner, run-testplan-simple.pl, which can be found at <a href="https://github.com/jetmore/TestLink-Misc/blob/master/scripts/run-testplan-simple.pl">https://github.com/jetmore/TestLink-Misc/blob/master/scripts/run-testplan-simple.pl</a> or inline and collapsed at the end of this post.  In terms of a full-featured test runner this is still pretty sparse, but it has some string points as an example:</p>
<ul>
<li>A demonstration of several more API methods than the standard perl example script</li>
<li>An attempt at a reusable error checking function for api responses</li>
<li>A method for storing external test information in test cases</li>
<li>Example of posting results back to TestLink via <code>tl.reportTCResult</code></li>
</ul>
<p>Based on the amount of Google hits I&#8217;ve seen on other TestLink posts I&#8217;ve made, it looks like the usage information for <code>tl.reportTCResult</code> will be useful for people.</p>
<p>In my opinion the most useful thing in this script is the generalized error checking.  The TestLink API is a mess in terms of standardized responses.  There&#8217;s no &#8220;this is an error response&#8221; flag, so you have to determine errors heuristically, which is less than ideal.  The <code>isResponseError()</code> function is my attempt to identify as many failure conditions as possible in one function.</p>
<p>The variables <code>$testServer</code>, <code>$APIKey</code>, and <code>$projectName</code> at the top of the script need to be uncommented and populated with the appropriate values.  An API Key for your TestLink install can be generated from your user page in the TestLink UI.</p>
<h2>Test Reporter</h2>
<p>The second script is a reporter.  Because we are in very early stages of implementing automated testing, we have many tests that are blocking (we wrote tests for functionality that isn&#8217;t implemented yet, and I chose to register that as a blocked test case in TestLink).  So, at the moment, individual test case status is not very interesting to me.  What is interesting is changes in status.  If a test passed two nights ago, but blocked last night, I want to know about it.</p>
<p>I wrote get-testplan-status.pl (<a href="https://github.com/jetmore/TestLink-Misc/blob/master/scripts/get-testplan-status.pl">https://github.com/jetmore/TestLink-Misc/blob/master/scripts/get-testplan-status.pl</a>) to do this reporting for me.  This script is largely similar to run-testplan-simple.pl above, but it is a read-only script, which might make it more interesting for initial exploring.</p>
<p>This script also makes use of the non-standard API call <code>getExecutionResults()</code>.  I wrote this new interface to allow more than one result to be returned for a test case.  See <a href="http://www.jetmore.org/john/blog/2012/03/missing-testlink-api-function-getexecutionresults/">http://www.jetmore.org/john/blog/2012/03/missing-testlink-api-function-getexecutionresults/</a> for more details on the function, including the code itself.</p>
<p>As with run-testplan-simple.pl, the variables <code>$testServer</code>, <code>$APIKey</code>, and <code>$projectName</code> at the top of the script need to be uncommented and populated with the appropriate values.</p>
<h2>Code</h2>
<p>The code for these two scripts at the time I wrote this post is included inline below, hopefully minimized&#8230;</p>
<p><strong><a href="https://github.com/jetmore/TestLink-Misc/blob/master/scripts/run-testplan-simple.pl">run-testplan-simple.pl</a></strong></p>
<pre class="brush: perl; collapse: true; light: false; title: ; toolbar: true; notranslate">
#!/usr/bin/perl

# This is a sanitized version of the &quot;nightly test runner&quot; I bashed up for my company.
# It is very unlikely to be usable verbatim, but it shows some fundamental concepts:
#	- how to interact w/ TestLink XMLRPC api with a perl client
#	- an attempt at an error checking function for api responses
#	- a method for storing external test information in test cases
#	- how to post results back to TestLink

# -- John Jetmore, 2012

# yum install perl-Crypt-SSLeay
# yum install perl-RPC-XML
# http://www.softwaretestingconcepts.com/test-automation-using-testlink-xmlrpc-api-steps-and-sample-python-client-program
# http://search.cpan.org/~rjray/RPC-XML-0.71/
# http://jetmore.org/john/misc/phpdoc-testlink193-api/TestlinkAPI/TestlinkXMLRPCServer.html

use strict;

use RPC::XML;
use RPC::XML::Client;
use Data::Dumper;

# !!! You have to change all three of these to valid local settings to get this to work:
#my $testServer  = 'https://localhost/testlink/lib/api/xmlrpc.php';
#my $APIKey      = 'PUT_YOUR_API_ACCESS_KEY_IN_HERE';
#my $projectName = 'PUT_THE_NAME_OF_YOUR_PROJECT_HERE';

my $resp;
my $method;
my $options;
my $log      = -t STDIN ? 1 : 0; # only log if we're being run via a term
my %statuses = ('passed' =&gt; 'p', 'failed' =&gt; 'f', 'blocked' =&gt; 'b');

my $planName    = shift || die &quot;Need Test Plan name to continue\n&quot;;

my $client = RPC::XML::Client-&gt;new($testServer);

#############
# Confirm test plan exists, get planID
$resp = $client-&gt;send_request('tl.getTestPlanByName', {
	'devKey' =&gt; $APIKey, 'testprojectname' =&gt; $projectName, testplanname =&gt; $planName
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getTestPlanByName ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp); # uncomment to see what the raw response looks like
my $projectID = $resp-&gt;value-&gt;[0]{testproject_id};
my $planID    = $resp-&gt;value-&gt;[0]{id};
print &quot;Found planID = $planID, projectID = $projectID\n&quot; if ($log);

############
# This just picks the first build returned and uses it.  Errors if no build available
# for test plan.
$resp = $client-&gt;send_request('tl.getBuildsForTestPlan', {
	'devKey' =&gt; $APIKey, 'testplanid' =&gt; $planID
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getBuildsForTestPlan ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp); # uncomment to see what the raw response looks like
my $buildID = $resp-&gt;value-&gt;[0]{id};
print &quot;Found buildID = $buildID\n&quot; if ($log);

###############
# Get all available test plan platforms.  This is kind of simple right now.  For now, if test runner is on linux, we will run
# any test case assigned to a plan with &quot;Linux&quot; in it, and same with Windows.  In future we probably want to filter
# on specific os releases and architectures
$resp = $client-&gt;send_request('tl.getTestPlanPlatforms', {
	'devKey' =&gt; $APIKey, 'testplanid' =&gt; $planID
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getTestPlanPlatforms ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp); # uncomment to see what the raw response looks like
my %platformInfo = ();
# This heuristic was specific to my company's use.  Tailor to taste.
foreach my $platform (@{$resp-&gt;value()}) {
	if ($^O eq 'linux' &amp;&amp; ($platform-&gt;{name} =~ /\bCentOS\b/i || $platform-&gt;{name} =~ /\bLinux\b/i)) {
		%platformInfo = %$platform;
		last;
	}
	elsif ($^O eq 'windows' &amp;&amp; $platform-&gt;{name} =~ /\bWindows\b/i) {
		%platformInfo = %$platform;
		last;
	}
}
if (!$platformInfo{id}) {
	die &quot;No available platform matched current platform\n&quot; .
	    &quot;Current: $^O\n&quot; .
	    &quot;Available: \n&quot; . Dumper($resp-&gt;value());
}
print &quot;Found platform = $platformInfo{id}, $platformInfo{name}\n&quot; if ($log);

#################
# Now get all test cases in the test plan.
$resp = $client-&gt;send_request('tl.getTestCasesForTestPlan', {
	'devKey' =&gt; $APIKey, 'testplanid' =&gt; $planID,
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getTestCasesForTestPlan ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp); # uncomment to see what the raw response looks like
my $respData  = $resp-&gt;value();
my %testCases = ();
# manually filter the test cases in the test plan against our currently selected platform
foreach my $caseID (keys %$respData) {
	foreach my $platformID (keys %{$respData-&gt;{$caseID}}) {
		if ($platformID eq $platformInfo{id} &amp;&amp; $respData-&gt;{$caseID}{$platformID}{active} == 1) {
			$testCases{$caseID}{$platformID} = $respData-&gt;{$caseID}{$platformID};
		}
	}
}
#print Dumper(\%testCases);
print &quot;Found &quot;, scalar(keys(%testCases)), &quot; test cases to run\n&quot; if ($log);

###########################
# Now we loop and execute
foreach my $caseID (keys %testCases) {

	# As far as I can tell there's no real standard for how external test information is stored
	# in a testlink testcase.  We chose to pursue:
	#	1) 1 external test script per test case
	#	2) external test &quot;configs&quot; are stored in the testcase's &quot;summary&quot; field
	#	3) configs are:
	#		- one config per line
	#		- line format is KEY: VALUE
	#	4) Specific keys:
	#		- TestScript - complete path, including executable, of external test script
	#		- TestScriptOptions - Any command line options for TestScript
	#		- TestType - free text right now, but allow us a way to tell the test executor to run
	#		             different tests
	foreach my $platformID (keys %{$testCases{$caseID}}) {
		my $notes       = &quot;&quot;;
		my $status      = &quot;&quot;;
		my $tc          = $testCases{$caseID}{$platformID};
		my $caseConfigs = getCaseConfigs($tc-&gt;{summary});

		if (!$caseConfigs-&gt;{TestScript}) {
			$notes  = &quot;Can't execute test, no TestScript set on test case&quot;;
			$status = $statuses{blocked};
		}
		else {
			($status,$notes) = getTestStatus($caseConfigs-&gt;{TestScript}, $caseConfigs-&gt;{TestScriptOptions});
		}
		$resp = $client-&gt;send_request('tl.reportTCResult', {
			'devKey'     =&gt; $APIKey,
			'testcaseid' =&gt; $caseID,
			'testplanid' =&gt; $planID,
			'status'     =&gt; $status,
			'buildid'    =&gt; $buildID,
			'notes'      =&gt; $notes,
			'platformid' =&gt; $platformID,
		});
		if (isResponseError($resp)) {
			print STDERR &quot;Unable to save result for TC:$caseID, platform:$platformID:\n&quot;,
			             &quot;\t($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;,
			             &quot;\t\$notes = $notes\n&quot;,
			             &quot;\t\$status = $status\n&quot;;
		}
		print &quot;TC:$caseID, P:$platformID, $status&quot;, ($notes =~ m|\n|sm ? '' : &quot;, $notes&quot;), &quot;\n&quot; if ($log);
	}
}

exit;

sub getTestStatus {
	my $testScript  = shift;
	my $testOptions = shift;
	$testScript    = &quot;/path/to/standard/test/repo/$testScript&quot; if ($testScript !~ m|^/|);

	if (!-e $testScript) {
		return($statuses{blocked}, &quot;$testScript does not exist&quot;);
	}
	elsif (!-x $testScript) {
		return($statuses{blocked}, &quot;$testScript is not executable&quot;);
	}

	# this needs to be more flexible long term (path to perl and base needs to be flexible)
	my $cmd = &quot;$testScript $testOptions 2&gt;&amp;1&quot;;
	if (!open(P, &quot;$cmd |&quot;)) {
		return($statuses{blocked}, &quot;Error opening pipe to $cmd: $!&quot;);
	}
	my $output = join('', &lt;P&gt;);
	close(P);

	if ($?) {
		return($statuses{failed}, $output);
	}
	elsif ($output =~ /TODO/) {
		# We're using perl test harnesses for our initial implementation, so we have a very specific
		# check here that, even if the external test script returned &quot;0&quot; (success), we still need
		# to mark the test case as blocked if there's any &quot;TODO&quot; individual tests

		# this might need to be changed, but for now if there's a TODO, set the status to block
		# to indicate that, while it's not a hard failure, it's still not &quot;right&quot; yet.
		return($statuses{blocked}, $output);
	}
	return($statuses{passed}, $output);
}

# take a case's summary field and return a ref to a hash containing key-&gt;value pairs.
# the summary field might look like this:
#&lt;div&gt;
#&lt;div&gt;TestScript: coreAAA-ui/Users.t&lt;/div&gt;
#&lt;div&gt;TestType: Standard&lt;/div&gt;
#&lt;/div&gt;
sub getCaseConfigs {
	my $summary = shift;
	my $null    = chr(0);
	my %config  = ();

	$summary =~ s|&lt;/div&gt;\n&lt;div&gt;|$null|smg;
	$summary =~ s|&lt;/?div&gt;|$null|g;
	$summary =~ s|$null{2,}|$null|;
	$summary =~ s|^$null+||;
	$summary =~ s|$null+$||;

	foreach my $pair (split(/$null/, $summary)) {
		next if ($pair =~ m|^\s*$|); # silently skip blank lines
		$pair =~ s|&lt;[^&gt;]+&gt;||g;
		$pair =~ s|\n||gsm;
		if ($pair !~ m|^\w+:\s|) {
			print STDERR &quot;saw config line '$pair', can't normalize to 'key: value', skipping\n&quot;;
			next;
		}
		else {
			my($k,$v) = split(/:\s+/, $pair, 2);
			$config{$k} = $v;
		}
	}

	return(\%config);
}

# Hoo boy is the API for TestLink messed up when it comes to error checking.  This is my best attempt
# at a single function that checks for error states in the returned object.  It works for every function
# I've used it on, but it wouldn't surprise me a bit if it incorrectlt reported an error condition
# for interfaces I haven't tried yet.

# takes a response from an API call and tells you if it is an error or not.
# returns one of the following:
# 0 - no error, we have data in the object
# 1 - API error - we received an error in the API response itself
# 2 - unknown local error
# 3 - unknown local HTTP level error (bad URL, remote host offline, etc)
# 4 - XMLRPC fault (for instance, incorrect RPC method name)
# 5 - unknown API response type (no data in response, unsure if this error type exists)
# 6 - code error in this module - shouldn't happen
# Also sets:
#    $main::apiLastResponseCode  - return code as described above
#    $main::apiLastResponseString - a text string describing the last api response (typically
#                                   something like the description above, plus any error text
#                                   that might have been returned by the tools
sub isResponseError {
	my $apiResp = shift;

	my $code = \$main::apiLastResponseCode;
	my $text = \$main::apiLastResponseString;

	$$code = 6;
	$$text = &quot;unknown subroutine error&quot;;

	if (!$apiResp) {
		$$code = 2;
		$$text = &quot;Unknown local error&quot;;
		#print STDERR &quot;Request failed, exiting\n&quot;;
		#exit 1;
	}
	elsif (!ref($apiResp)) {
		# can get this by using incorrect url (I added &quot;2&quot; to the end of the URL)
		$$code = 3;
		$$text = &quot;Local request error: $apiResp&quot;;
	}
	elsif ($apiResp-&gt;is_fault) {
		# can get this by using an incorrect method (I changed method to &quot;getTestPlanByName2&quot;)
		#print STDERR &quot;XMLRPC Fault: &quot;, $resp-&gt;value-&gt;{faultCode}, &quot;: &quot;, $resp-&gt;value-&gt;{faultString}, &quot;\n&quot;;
		#exit 3;
		$$code = 4;
		$$text = 'XMLRPC Fault: ' . $resp-&gt;value-&gt;{faultCode} . ': ' . $resp-&gt;value-&gt;{faultString};
	}
	else {
		my $respData = $resp-&gt;value;

		#print Dumper($respData), &quot;\n&quot;;

		# note every valid response returns an array ref for data (see getTestCasesForTestPlan), so
		# split our error checking
		if (ref($respData) eq 'ARRAY') {
			if (scalar(@$respData) == 0) {
				# not sure if this is a real condition or not
				#print STDERR &quot;Unexpected response from server: No objects in response\n&quot;;
				#exit 4;
				$$code = 5;
				$$text = &quot;Unexpected response from server: No objects in response&quot;;
			}
			# there's no single &quot;API Error&quot; flag, this is the best test I could come up with
			# It is an API error response if:
			#  - the data is an array ref
			#  - the first element in the array is a hash ref
			#  - that hash has exactly two keys
			#  - the two keys are {code} and {message}
			# One interesting thing to note is that you can have two error objects (for instance, if you make a
			# getTestPlanByName call with an invalid API Key, you will get two errors, the first will be &quot;Invalid
			# API Key&quot; and the second will be &quot;Test Plan Not Found&quot;).  It appears that the first is the most relevant,
			# so I'll be using it as if it were the sole error
			#elsif (isError($respData)) {
			elsif (ref($respData-&gt;[0]) eq 'HASH' &amp;&amp; scalar(keys(%{$respData-&gt;[0]})) == 2 &amp;&amp;
					exists($respData-&gt;[0]{code}) &amp;&amp; exists($respData-&gt;[0]{message})) {
				# simulate by passing in a plan name that doesn't exist
				#print &quot;Unable to find testplan '$planName': ($respData-&gt;[0]{code}) $respData-&gt;[0]{message}\n&quot;;
				#exit 5;
				$$code = 1;
				$$text = &quot;($respData-&gt;[0]{code}) $respData-&gt;[0]{message}&quot;;
			}
			# turing off the checking for this state, I don't think it's a global error condition
			#elsif (scalar(@$respData) &gt; 1) {
			#	# not sure if this is a real error state or not
			#	print STDERR &quot;Something unexpected happened - received &gt;1 items in response to getTestPlanByName:\n&quot;, Dumper($respData), &quot;\n&quot;;
			#	exit 6;
			#}
			else {
				$$code = 0;
				$$text = &quot;&quot;;
			}
		}
		# I haven't seen very many example of the data key being a hash, assume it's always ok for now
		elsif (ref($respData) eq 'HASH') {
			$$code = 0;
			$$text = &quot;&quot;;
		}
	}
	return $$code;
}
</pre>
<p><strong><a href="https://github.com/jetmore/TestLink-Misc/blob/master/scripts/get-testplan-status.pl">get-testplan-status.pl</a></strong></p>
<pre class="brush: perl; collapse: true; light: false; title: ; toolbar: true; notranslate">
#!/usr/bin/perl

# yum install perl-Crypt-SSLeay
# yum install perl-RPC-XML
# http://www.softwaretestingconcepts.com/test-automation-using-testlink-xmlrpc-api-steps-and-sample-python-client-program
# http://search.cpan.org/~rjray/RPC-XML-0.71/
# http://jetmore.org/john/misc/phpdoc-testlink193-api/TestlinkAPI/TestlinkXMLRPCServer.html

use strict;

use RPC::XML;
use RPC::XML::Client;
use Data::Dumper;

my $dumpRaw    = 0;
# !!! You have to change all three of these to valid local settings to get this to work:
#my $testServer  = 'https://localhost/testlink/lib/api/xmlrpc.php';
#my $APIKey      = 'PUT_YOUR_API_ACCESS_KEY_IN_HERE';
#my $projectName = 'PUT_THE_NAME_OF_YOUR_PROJECT_HERE';

my $resp;
my $method;
my $options;
my $log      = -t STDIN ? 1 : 0; # only log if we're being run via a term
my %statuses = ('passed' =&gt; 'p', 'failed' =&gt; 'f', 'blocked' =&gt; 'b');

my $planName    = shift || die &quot;Need Test Plan name to continue\n&quot;;

my $client = RPC::XML::Client-&gt;new($testServer);

#############
# Confirm test plan exists, get planID
$resp = $client-&gt;send_request('tl.getTestPlanByName', {
	'devKey' =&gt; $APIKey, 'testprojectname' =&gt; $projectName, testplanname =&gt; $planName
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getTestPlanByName ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp);
my $projectID = $resp-&gt;value-&gt;[0]{testproject_id};
my $planID    = $resp-&gt;value-&gt;[0]{id};
print &quot;Found planID = $planID, projectID = $projectID\n&quot; if ($log);

############
# In the future, get all builds, pick the right one if it exists, or create new.
# For now though, just assign all tests to the 'HEAD' build
$resp = $client-&gt;send_request('tl.getBuildsForTestPlan', {
	'devKey' =&gt; $APIKey, 'testplanid' =&gt; $planID
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getBuildsForTestPlan ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp);
my $buildID = $resp-&gt;value-&gt;[0]{id};
print &quot;Found buildID = $buildID\n&quot; if ($log);

###############
# Get all available test plan platforms.  This is kind of simple right now.  For now, if test runner is on linux, we will run
# any test case assigned to a plan with &quot;Linux&quot; in it, and same with Windows.  In future we probably want to filter
# on specific os releases and architectures
$resp = $client-&gt;send_request('tl.getTestPlanPlatforms', {
	'devKey' =&gt; $APIKey, 'testplanid' =&gt; $planID
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getTestPlanPlatforms ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp);
my %platformInfo = ();
foreach my $platform (@{$resp-&gt;value()}) {
	$platformInfo{$platform-&gt;{id}} = $platform;
}

#################
# Now get all test cases in the test plan.
$resp = $client-&gt;send_request('tl.getTestCasesForTestPlan', {
	'devKey' =&gt; $APIKey, 'testplanid' =&gt; $planID,
});
if (isResponseError($resp)) {
	die &quot;An error occurred in getTestCasesForTestPlan ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
}
#print Dumper($resp);
my $respData  = $resp-&gt;value();
my %testCases = ();
# manually filter the test cases in the test plan against our currently selected platform
foreach my $caseID (keys %$respData) {
	foreach my $platformID (keys %{$respData-&gt;{$caseID}}) {
	#	if ($platformID eq $platformInfo{id} &amp;&amp; $respData-&gt;{$caseID}{$platformID}{active} == 1) {
			$testCases{$caseID}{$platformID} = $respData-&gt;{$caseID}{$platformID};
	#	}
	}
}
#print Dumper(\%testCases);
print &quot;Found &quot;, scalar(keys(%testCases)), &quot; test cases to run\n&quot; if ($log);

###########################
# Now we loop and execute
my %tcResults = ();
foreach my $caseID (keys %testCases) {
	$resp = $client-&gt;send_request('tl.getExecutionResults', {
		'devKey' =&gt; $APIKey, 'testplanid' =&gt; $planID, 'testcaseid' =&gt; $caseID, 'numexecs' =&gt; 2,
	});
	if (isResponseError($resp)) {
		die &quot;An error occurred in getExecutionResult ($main::apiLastResponseCode): $main::apiLastResponseString\n&quot;;
	}
	$tcResults{$caseID}{results} = $resp-&gt;value();
	$tcResults{$caseID}{configs} = getCaseConfigs($testCases{$caseID}{(keys(%{$testCases{$caseID}}))[0]}{summary});
	#print Dumper($resp);
}

# this is super quick and dirty.  Long term I would like to see an email that has an attachment which is a CSV file
# with values like this:
# package,component,testScript,platform,ExecDate,Status
# CoreDevice,Categories,Categories.t,server - CentOS 5.5 x86_64,2012-03-27 15:31:03,failed
# and with an easily-readible body that includes HTML like the following (only include lines for case/platform
# pairs that have changed status:
# TCID,TestScript,Platform,prevExecTime,lastExecTime
# 1234,codeDevice-ui/Categories.t,server - CentOS 5.5 x86_64,2012-03-26 14:31:03,2012-03-27 15:31:03
# the key to the HTML in the body is that the BGcolor for the last two cells will indicate the status of
# that test run (green = passed, red = failed, blue = blocked, black = not run).  This allows an easily-scannable
# list of _changed_ test cases.

# For now though, just use text.  Also ignore platform for right now.
foreach my $caseID (keys %tcResults) {
	my $r = $tcResults{$caseID}{results};
	my $c = $tcResults{$caseID}{configs};
	# skip if it's never been run
	next if ($r-&gt;[0]{id} == -1);
	# skip if there's not a change in the results
	next if ($r-&gt;[0]{status} eq $r-&gt;[1]{status});

	#print &quot;$caseID $tcResults{$caseID}{configs}{TestScript} $r-&gt;[1]{status} -&gt; $r-&gt;[0]{status}\n&quot;;
	printf &quot;%-8s %6s %s\n               %3s %20s -&gt; %3s %20s\n&quot;,
		$caseID,
		&quot;$r-&gt;[1]{status} -&gt; $r-&gt;[0]{status}&quot;,
		$c-&gt;{TestScript},
		$r-&gt;[1]{platform_id}, $r-&gt;[1]{execution_ts},
		$r-&gt;[0]{platform_id}, $r-&gt;[0]{execution_ts};
}

exit;

# take a case's summary field and return a ref to a hash containing key-&gt;value pairs.
# the summary field might look like this:
#&lt;div&gt;
#&lt;div&gt;TestScript: coreAAA-ui/Users.t&lt;/div&gt;
#&lt;div&gt;TestType: Standard&lt;/div&gt;
#&lt;/div&gt;
sub getCaseConfigs {
	my $summary = shift;
	my $null    = chr(0);
	my %config  = ();

	$summary =~ s|&lt;/div&gt;\n&lt;div&gt;|$null|smg;
	$summary =~ s|&lt;/?div&gt;|$null|g;
	$summary =~ s|$null{2,}|$null|;
	$summary =~ s|^$null+||;
	$summary =~ s|$null+$||;

	foreach my $pair (split(/$null/, $summary)) {
		next if ($pair =~ m|^\s*$|); # silently skip blank lines
		$pair =~ s|&lt;[^&gt;]+&gt;||g;
		$pair =~ s|\n||gsm;
		if ($pair !~ m|^\w+:\s|) {
			print STDERR &quot;saw config line '$pair', can normalize to 'key: value', skipping\n&quot;;
			next;
		}
		else {
			my($k,$v) = split(/:\s+/, $pair, 2);
			$config{$k} = $v;
		}
	}

	return(\%config);
}

# takes a response from an API call and tells you if it is an error or not.
# returns one of the following:
# 0 - no error, we have data in the object
# 1 - API error - we received an error in the API response itself
# 2 - unknown local error
# 3 - unknown local HTTP level error (bad URL, remote host offline, etc)
# 4 - XMLRPC fault (for instance, incorrect RPC method name)
# 5 - unknown API response type (no data in response, unsure if this error type exists)
# 6 - code error in this module - shouldn't happen
# Also sets:
#    $main::apiLastResponseCode  - return code as described above
#    $main::apiLastResponseString - a text string describing the last api response (typically
#                                   something like the description above, plus any error text
#                                   that might have been returned by the tools
sub isResponseError {
	my $apiResp = shift;

	my $code = \$main::apiLastResponseCode;
	my $text = \$main::apiLastResponseString;

	$$code = 6;
	$$text = &quot;unknown subroutine error&quot;;

	if (!$apiResp) {
		$$code = 2;
		$$text = &quot;Unknown local error&quot;;
		#print STDERR &quot;Request failed, exiting\n&quot;;
		#exit 1;
	}
	elsif (!ref($apiResp)) {
		# can get this by using incorrect url (I added &quot;2&quot; to the end of the URL)
		$$code = 3;
		$$text = &quot;Local request error: $apiResp&quot;;
	}
	elsif ($apiResp-&gt;is_fault) {
		# can get this by using an incorrect method (I changed method to &quot;getTestPlanByName2&quot;)
		#print STDERR &quot;XMLRPC Fault: &quot;, $resp-&gt;value-&gt;{faultCode}, &quot;: &quot;, $resp-&gt;value-&gt;{faultString}, &quot;\n&quot;;
		#exit 3;
		$$code = 4;
		$$text = 'XMLRPC Fault: ' . $resp-&gt;value-&gt;{faultCode} . ': ' . $resp-&gt;value-&gt;{faultString};
	}
	else {
		my $respData = $resp-&gt;value;

		#print Dumper($respData), &quot;\n&quot;;

		# note every valid response returns an array ref for data (see getTestCasesForTestPlan), so
		# split our error checking
		if (ref($respData) eq 'ARRAY') {
			if (scalar(@$respData) == 0) {
				# not sure if this is a real condition or not
				#print STDERR &quot;Unexpected response from server: No objects in response\n&quot;;
				#exit 4;
				$$code = 5;
				$$text = &quot;Unexpected response from server: No objects in response&quot;;
			}
			# there's no single &quot;API Error&quot; flag, this is the best test I could come up with
			# It is an API error response if:
			#  - the data is an array ref
			#  - the first element in the array is a hash ref
			#  - that hash has exactly two keys
			#  - the two keys are {code} and {message}
			# One interesting thing to note is that you can have two error objects (for instance, if you make a
			# getTestPlanByName call with an invalid API Key, you will get two errors, the first will be &quot;Invalid
			# API Key&quot; and the second will be &quot;Test Plan Not Found&quot;).  It appears that the first is the most relevant,
			# so I'll be using it as if it were the sole error
			#elsif (isError($respData)) {
			elsif (ref($respData-&gt;[0]) eq 'HASH' &amp;&amp; scalar(keys(%{$respData-&gt;[0]})) == 2 &amp;&amp;
					exists($respData-&gt;[0]{code}) &amp;&amp; exists($respData-&gt;[0]{message})) {
				# simulate by passing in a plan name that doesn't exist
				#print &quot;Unable to find testplan '$planName': ($respData-&gt;[0]{code}) $respData-&gt;[0]{message}\n&quot;;
				#exit 5;
				$$code = 1;
				$$text = &quot;($respData-&gt;[0]{code}) $respData-&gt;[0]{message}&quot;;
			}
			# turing off the checking for this state, I don't think it's a global error condition
			#elsif (scalar(@$respData) &gt; 1) {
			#	# not sure if this is a real error state or not
			#	print STDERR &quot;Something unexpected happened - received &gt;1 items in response to getTestPlanByName:\n&quot;, Dumper($respData), &quot;\n&quot;;
			#	exit 6;
			#}
			else {
				$$code = 0;
				$$text = &quot;&quot;;
			}
		}
		# I haven't seen very many example of the data key being a hash, assume it's always ok for now
		elsif (ref($respData) eq 'HASH') {
			$$code = 0;
			$$text = &quot;&quot;;
		}
	}
	return $$code;
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/05/example-testlink-xmlrpc-clients-in-perl/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Multiple Spaces After Period in WordPress</title>
		<link>http://www.jetmore.org/john/blog/2012/03/multiple-spaces-after-period-in-wordpress/</link>
		<comments>http://www.jetmore.org/john/blog/2012/03/multiple-spaces-after-period-in-wordpress/#comments</comments>
		<pubDate>Fri, 30 Mar 2012 18:00:27 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[oss]]></category>
		<category><![CDATA[tweaks]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=543</guid>
		<description><![CDATA[It&#8217;s bugged me for a while that my WordPress posts would often have an extra space at the beginning of a line. It would only happen when it was a new sentence, and after a while I realized it was &#8230; <a href="http://www.jetmore.org/john/blog/2012/03/multiple-spaces-after-period-in-wordpress/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s bugged me for a while that my WordPress posts would often have an extra space at the beginning of a line.  It would only happen when it was a new sentence, and after a while I realized it was related to my habit of typing two spaces after a period.</p>
<div id="attachment_545" class="wp-caption aligncenter" style="width: 254px"><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/Capture.png"><img src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/Capture.png" alt="" title="Capture" width="244" height="167" class="size-full wp-image-545" /></a><p class="wp-caption-text">Fear my drawing skills...</p></div>
<p><span id="more-543"></span></p>
<p>I finally got grumpy enough to try to find a solution for it today.  The obvious &#8220;edit every old post to remove double-spaces&#8221; seemed inefficient, and frankly I&#8217;m not likely to retrain myself to use only one space for future posts.</p>
<p>I assumed this would be a well known issue and there would a tuneable or a plugin to address it, but it turned out to be really hard to Google the right terms.  I have actually decided not to change anything on my system based on my findings, but I wanted to document what I learned.</p>
<p>First, I did find a few plugins, but nothing that looked especially well-made.  I never even installed one, because they all looked so suspect that I assumed they would reformat all my code snippets also.  I care much more about the formatting in those code snippets than I do the double-space issue.</p>
<p>Next, after looking at the page source and seeing that there were two spaces present and not, say &#8216;&amp;nbsp;&#8217; characters (*foreshadowing*), I tried to reproduce the problem outside of WP, mimicking the WP CSS.  No luck.</p>
<p>I finally managed to Google up this <a href="http://wordpress.org/support/topic/weird-spacing-between-sentences">WP support page</a> where the reporter is describing the exact issue I am having.  What&#8217;s interesting to me is that this is a relatively new ticket and he&#8217;s told that there&#8217;s no solution.  It does point out what&#8217;s actually happening though:</p>
<blockquote><p>What happens is that when you put two spaces in a row in the visual editor, wordpress converts it to &#8220;space + unicode_nbsp&#8221;</p></blockquote>
<p>Ah.  That&#8217;s right, always use tools that won&#8217;t lie to me.  Of course view-source is just translating that unicode character (correctly) to a space.  That&#8217;s what the character is supposed to look like.  I can confirm that&#8217;s what&#8217;s happening though.  I grabbed a copy of a post with wget and looked at the output with xxd:</p>
<div id="attachment_550" class="wp-caption aligncenter" style="width: 481px"><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/Capture21.png"><img src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/Capture21.png" alt="" title="Capture2" width="471" height="32" class="size-full wp-image-550" /></a><p class="wp-caption-text">&quot;20&quot; is the real space, &quot;c2a0&quot; is  U+00A0, the Unicode non-breaking space.</p></div>
<p>While that page itself doesn&#8217;t offer a solution, it mentions <a href="http://core.trac.wordpress.org/ticket/19936">WP bug 19936</a>, and that bug actually contains the solution.  Essentially, the root of the problem is the TinyMCE editor that is used to provide the WYSIWYG functionality of the WP &#8220;Visual&#8221; editor.  TinyMCE was the component translating trailing spaces into non-breaking spaces.  That ticket is actually closed because the 3.4 release will include a newer version of TinyMCE which employs slightly better logic about which space to make non-breaking.</p>
<p>Again, none of that really helps me right now, and I suspect that it won&#8217;t help old posts in the future either because, once it&#8217;s been saved that way in the DB, the trailing space has already been translated to a non-breaking space (it just looks like a regular space in the text editor).  Also, 3.4 isn&#8217;t released yet, so that&#8217;s not a help on newer posts.</p>
<p>What does work, though, is just using the HTML editor instead of the Visual editor.  Normally I would say that this is a poor solution, but I had already sworn off the Visual editor in the past week after getting too fed up with how it irreparably mangles code snippets.</p>
<p>I still don&#8217;t have a solution for old posts that I created in the visual editor.  I toyed with the idea of creating some javascript to do it dynamically but I don&#8217;t really care enough to.  I may go back and edit the articles that still show up on the main page, but in the end just knowing the cause of the problem and how to avoid/fix it in the future is enough for right now.</p>
<p><strong>Update:</strong> I actually went back and edited the last 10 posts, replacing the unicode_nonbreak with a regular ASCII space character.  I wasn&#8217;t going to, but I figured out a relatively easy way to do it.  I had feared I wouldn&#8217;t be able to do a bulk find and replace for fear of harming other formatting.  I found that a good text editor (I used Notepad++) can do a find and replace on the specific U+00A0 character very quickly.  I couldn&#8217;t figure out how to type that character, but I could copy it from the post and paste it into the search box, then type a regular space into the replace box.  Since the find/replace box stays open all the time, I could copy the text out of the post, paste it into n++, replace all, copy out of n++, and paste into WP very quickly.  Once I figure out what I was doing it took about 3 minutes to fix those 10 posts.  If I had a very large site I would probably write a tool to do a find/replace against the DB directly, but this solution worked fine for me.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/03/multiple-spaces-after-period-in-wordpress/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Missing TestLink API Function &#8211; getExecutionResults()</title>
		<link>http://www.jetmore.org/john/blog/2012/03/missing-testlink-api-function-getexecutionresults/</link>
		<comments>http://www.jetmore.org/john/blog/2012/03/missing-testlink-api-function-getexecutionresults/#comments</comments>
		<pubDate>Wed, 28 Mar 2012 19:21:55 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[oss]]></category>
		<category><![CDATA[testlink]]></category>
		<category><![CDATA[tweaks]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=491</guid>
		<description><![CDATA[I have finally come to terms with the TestLink API and have bashed out an automated test runner written in perl (which I hope to clean up and post in the next few days, there don&#8217;t seem to be many &#8230; <a href="http://www.jetmore.org/john/blog/2012/03/missing-testlink-api-function-getexecutionresults/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I have finally come to terms with the TestLink API and have bashed out an automated test runner written in perl (which I hope to clean up and post in the next few days, there don&#8217;t seem to be many (or any) examples of this in the wild).</p>
<p>After getting nightly automated testing running, I wanted a very simple script which would, for each test case in a given test plan, compare the last and the second to last result and print a warning if they were different.  Long term I will have better automated reporting, but our test script library is still so new that it&#8217;s not really news that a lot of them are failing.  What would be news is if a specific test case passed two nights ago but failed last night.</p>
<p><span id="more-491"></span></p>
<p>This seemed like a really easy thing to do, but I couldn&#8217;t find an API function to support it.  The closest thing I could find was <code>getLastExecutionResult()</code>, but that only gave me the last execution, it didn&#8217;t give me the execution history I needed.  I also could have leveraged the fact that <code>getTestCasesForTestPlan()</code> includes each case&#8217;s last execution status to have done in-place alarming if the new status didn&#8217;t match the old status, but I felt like that would limit my ability to expand reporting in the future.  I toyed with the idea of getting the info out of the DB directly, but decided instead of just implement the API function I needed.  It&#8217;s mostly a copy of <code>getLastExecutionResult()</code>, but it has an added parameter <code>numexecs</code> which allows you to specify how many executions to return.</p>
<p>I hope to wrap all of these TestLink patches up into a better place at some point in the future, but for now it feels better to just get the code out where Google can find it as fast as possible.</p>
<p>Here&#8217;s the patch for adding the <code>getExecutionResults()</code> function:</p>
<blockquote><p><strong>Update 2012-05-08</strong>: There&#8217;s a <a href="https://github.com/jetmore/TestLink-Misc/blob/master/patches/getExecutionResults.patch">real, usable-with-patch-command patch file for this change</a> available at the <a href="https://github.com/jetmore/TestLink-Misc">github repo I started for my TestLink changes</a>.</p></blockquote>
<pre class="brush: diff; title: ; notranslate">
--- xmlrpc.class.php	(revision 268)
+++ xmlrpc.class.php	(revision 270)
@@ -176,6 +176,7 @@
 	public static $testSuiteNameParamName = &quot;testsuitename&quot;;
 	public static $timeStampParamName = &quot;timestamp&quot;;
     public static $titleParamName = &quot;title&quot;;
+    public static $numexecsParamName = &quot;numexecs&quot;;

     public static $urgencyParamName = &quot;urgency&quot;;
@@ -255,6 +256,7 @@
 	                            'tl.getBuildsForTestPlan' =&gt; 'this:getBuildsForTestPlan',
 	                            'tl.getLatestBuildForTestPlan' =&gt; 'this:getLatestBuildForTestPlan',
                                 'tl.getLastExecutionResult' =&gt; 'this:getLastExecutionResult',
+                                'tl.getExecutionResults' =&gt; 'this:getExecutionResults',
 	                            'tl.getTestSuitesForTestPlan' =&gt; 'this:getTestSuitesForTestPlan',
 	                            'tl.getTestSuitesForTestSuite' =&gt; 'this:getTestSuitesForTestSuite',
 	                            'tl.getTestCasesForTestSuite'	=&gt; 'this:getTestCasesForTestSuite',
@@ -1220,8 +1222,78 @@

 		return $maxbuild;
 	}
-
+
 	/**
+	 * Gets the result of LAST EXECUTION for a particular testcase
+	 * on a test plan, but WITHOUT checking for a particular build
+	 *
+	 * @param struct $args
+	 * @param string $args[&quot;devKey&quot;]
+	 * @param int $args[&quot;tplanid&quot;]
+	 * @param int $args[&quot;testcaseid&quot;]: optional, if does not is present
+	 *                                 testcaseexternalid must be present
+	 *
+	 * @param int $args[&quot;testcaseexternalid&quot;]: optional, if does not is present
+	 *                                         testcaseid must be present
+	 * @param int $args[&quot;numexecs&quot;]: optional.  If set, number of cases to
+	 *                               return.  If unset, all will be returned
+	 *
+	 * @return mixed $resultInfo
+	 *               if execution found, array with these keys:
+	 *               id (execution id),build_id,tester_id,execution_ts,
+	 *               status,testplan_id,tcversion_id,tcversion_number,
+	 *               execution_type,notes.
+	 *
+	 *               if test case has not been execute,
+	 *               array('id' =&gt; -1)
+	 *
+	 * @access public
+	 */
+	public function getExecutionResults($args)
+	{
+		$operation=__FUNCTION__;
+		$msg_prefix=&quot;({$operation}) - &quot;;
+
+		$this-&gt;_setArgs($args);
+		$resultInfo = array();
+		$status_ok=true;
+
+		// Checks are done in order
+		$checkFunctions = array('authenticate','checkTestPlanID','checkTestCaseIdentity');
+
+		$status_ok=$this-&gt;_runChecks($checkFunctions,$msg_prefix) &amp;&amp;
+		           $this-&gt;_checkTCIDAndTPIDValid(null,$msg_prefix) &amp;&amp;
+		           $this-&gt;userHasRight(&quot;mgt_view_tc&quot;);
+
+		if( $status_ok )
+		{
+			// get all, then return last
+			$sql = &quot; SELECT * FROM {$this-&gt;tables['executions']} &quot; .
+			       &quot; WHERE testplan_id = {$this-&gt;args[self::$testPlanIDParamName]} &quot; .
+			       &quot; AND tcversion_id IN (&quot; .
+			       &quot; SELECT id FROM {$this-&gt;tables['nodes_hierarchy']} &quot; .
+			       &quot; WHERE parent_id = {$this-&gt;args[self::$testCaseIDParamName]})&quot; .
+			       &quot; ORDER BY id DESC&quot;;
+
+			$limit   = $this-&gt;args[self::$numexecsParamName] &gt; 0 ? $this-&gt;args[self::$numexecsParamName] : -1;
+			$records = $this-&gt;dbObj-&gt;get_recordset($sql, null, $limit);
+
+			if(null == $records)
+			{
+				// has not been executed
+				// execution id = -1 =&gt; test case has not been runned.
+				$resultInfo[]=array('id' =&gt; -1);
+			}
+			else
+			{
+				$resultInfo = $records;
+			}
+		}
+
+		return $status_ok ? $resultInfo : $this-&gt;errors;
+	}
+
+	/**
      * Gets the result of LAST EXECUTION for a particular testcase
      * on a test plan, but WITHOUT checking for a particular build
      *
</pre>
<p>This function suffers from a few warts, but they are ones mostly inherited from <code>getLastExecutionResult()</code>.  If I were to polish this up (beyond my normal TestLink API beefs like some sort of standardized return format), I would definitely add optional <code>buildid</code> and <code>platformid</code> parameters so you could narrow down the executions you&#8217;re looking for even more.  But that&#8217;s a problem for another day&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/03/missing-testlink-api-function-getexecutionresults/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Expect scripts, [exec], and SIGCHLD</title>
		<link>http://www.jetmore.org/john/blog/2012/03/expect-scripts-exec-and-sigchld/</link>
		<comments>http://www.jetmore.org/john/blog/2012/03/expect-scripts-exec-and-sigchld/#comments</comments>
		<pubDate>Wed, 28 Mar 2012 18:33:51 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[oss]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=483</guid>
		<description><![CDATA[I had to debug an expect script that would not run in a specific environment today, and the solution was so frustrating that I had to write it down. In short, expect&#8217;s [exec] functionality requires SIGCHLD to be functional to &#8230; <a href="http://www.jetmore.org/john/blog/2012/03/expect-scripts-exec-and-sigchld/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I had to debug an expect script that would not run in a specific environment today, and the solution was so frustrating that I had to write it down.</p>
<p>In short, expect&#8217;s [exec] functionality requires SIGCHLD to be functional to work correctly.  If you have ignored the signal, you get a very nice explanation of why your script has just died.  For instance:</p>
<p><span id="more-483"></span></p>
<pre class="brush: plain; highlight: [1,5]; light: true; title: ; notranslate">
lappy 0 /home/jetmore/swap &gt; cat ./run-expect
#!/usr/bin/expect
trap SIG_IGN SIGCHLD
puts &quot;[exec date]: Expect: This is stdout&quot;
lappy 0 /home/jetmore/swap &gt; ./run-expect
Wed Mar 28 13:16:44 CDT 2012
error waiting for process to exit: child process lost (is SIGCHLD ignored or trapped?)
    while executing
&quot;exec date&quot;
    invoked from within
&quot;puts &quot;[exec date]: Expect: This is stdout&quot;&quot;
    (file &quot;./run-expect&quot; line 3)
</pre>
<p>This is great if you&#8217;re actually seeing the script&#8217;s STDERR.  As it happened I was presented with this problem in an environment in which, coincidentally, the tool executing the expect script was also throwing away STDERR.  Any attempt I made to capture STDERR or STDOUT made the script start working.  A heisenbug.  I finally saw the error above by attaching the the expect script using <code>strace -e write=2 -p $PID</code></p>
<p>After confirming that the calling tool was indeed setting SIGCHLD to IGNORE and that manually ignoring SIGCHLD broke the script (as in the example above), I found that the following script (which sets SIGCHLD back the the system&#8217;s default behavior) would survive even if the caller had IGNOREd SIGCHLD:</p>
<pre class="brush: plain; light: true; title: ; notranslate">
#!/usr/bin/expect
trap SIG_DFL SIGCHLD
puts &quot;[exec date]: Expect: This is stdout&quot;
</pre>
<p>So, the hopefully-googleable moral of the story is that expect&#8217;s exec function requires that SIGCHLD not be set to ignore, and this should be checked if your expect script is working in one environment but not another (ie, working from command line but failing when called by another program).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/03/expect-scripts-exec-and-sigchld/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Swaks Release 20120320.0 Available</title>
		<link>http://www.jetmore.org/john/blog/2012/03/swaks-release-20120320-0-available/</link>
		<comments>http://www.jetmore.org/john/blog/2012/03/swaks-release-20120320-0-available/#comments</comments>
		<pubDate>Wed, 21 Mar 2012 03:28:02 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[swaks]]></category>
		<category><![CDATA[oss]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=441</guid>
		<description><![CDATA[A new version of swaks is currently available for download. Downloads: Project Page: http://jetmore.org/john/code/swaks/ v20120320.0 distribution: http://jetmore.org/john/code/swaks/swaks-20120320.0.tar.gz v20120320.0 script only: http://jetmore.org/john/code/swaks/swaks-20120320.0/swaks v20120320.0 reference: http://jetmore.org/john/code/swaks/swaks-20120320.0/doc/ref.txt v20120320.0 changelog: http://jetmore.org/john/code/swaks/swaks-20120320.0/doc/Changes.txt IPv6 Support The largest feature of this release is IPv6 support. This functionality &#8230; <a href="http://www.jetmore.org/john/blog/2012/03/swaks-release-20120320-0-available/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>A new version of swaks is currently available for download.</p>
<h3><strong>Downloads:</strong></h3>
<ul>
<li><strong>Project Page</strong>: <a href="http://jetmore.org/john/code/swaks/">http://jetmore.org/john/code/swaks/</a></li>
<li><strong>v20120320.0 distribution</strong>: <a href="http://jetmore.org/john/code/swaks/swaks-20120320.0.tar.gz">http://jetmore.org/john/code/swaks/swaks-20120320.0.tar.gz</a></li>
<li><strong><strong>v20120320.0</strong> script only</strong>: <a href="http://jetmore.org/john/code/swaks/swaks-20120320.0/swaks">http://jetmore.org/john/code/swaks/swaks-20120320.0/swaks</a></li>
<li><strong><strong>v20120320.0</strong> reference</strong>: <a href="http://jetmore.org/john/code/swaks/swaks-20120320.0/doc/ref.txt">http://jetmore.org/john/code/swaks/swaks-20120320.0/doc/ref.txt</a></li>
<li><strong><strong>v20120320.0</strong> changelog</strong>: <a href="http://jetmore.org/john/code/swaks/swaks-20120320.0/doc/Changes.txt">http://jetmore.org/john/code/swaks/swaks-20120320.0/doc/Changes.txt</a></li>
</ul>
<p><span id="more-441"></span></p>
<h3><strong>IPv6 Support</strong></h3>
<p style="padding-left: 30px;">The largest feature of this release is IPv6 support.  This functionality survived a <a href="http://www.jetmore.org/john/blog/2012/02/swaks-ipv6-support-initial-draft/">reasonable pre-release test period</a> largely intact.  I am still not convinced that my handling of MX records that point to both IPv4 and IPv6 domains is the best, but I didn&#8217;t receive any feedback on alternate solutions.  I decided to release as is and see how my initial implementation held up to wider use.</p>
<h3><strong>New Features</strong>:</h3>
<ul>
<li>-4 and -6 options to force IPv4 or IPv6.</li>
<li>Added &#8211;local-port option</li>
<li>Added &#8211;dump-as-body and &#8211;dump-as-body-shows-password options, allowing session configuration information to be sent in the test email itself.  (Suggested by Chris Pimlott)</li>
<li>&#8211;dump (and therefore &#8211;dump-as-body) includes a new line for a &#8220;reconstructed command line&#8221;.  This is a sort of a synthetic command line including the real command line, any environment variable configs, and any config files.</li>
</ul>
<h3><strong>Notable Changes:</strong></h3>
<ul>
<li>The DIGEST-SHA1 authentication type now requires the Digest::SHA perl module instead of Digest::SHA1.  Digest::SHA1 has always been an extra install step, while Digest::SHA has been in the core perl distribution for many years. (Suggested by Andreas Metzler)</li>
<li>The -m option, marked deprecated since 2007, has been removed.</li>
</ul>
<h3><strong>Notable Bugs Fixed:</strong></h3>
<ul>
<li>In some very rare cases, a server response can be a base64-encoded success response, or a plaintext failure.  Previously in that situation swaks did not handle the case of the plaintext error and the &#8211;auth-plaintext option well, resulting in confusing output.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/03/swaks-release-20120320-0-available/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Oh The Things I Don&#8217;t Know! &#8211; AVR ISP Edition</title>
		<link>http://www.jetmore.org/john/blog/2012/03/oh-the-things-i-dont-know-avr-isp-edition/</link>
		<comments>http://www.jetmore.org/john/blog/2012/03/oh-the-things-i-dont-know-avr-isp-edition/#comments</comments>
		<pubDate>Fri, 09 Mar 2012 18:55:20 +0000</pubDate>
		<dc:creator>jetmore</dc:creator>
				<category><![CDATA[Hardware]]></category>
		<category><![CDATA[Projects]]></category>

		<guid isPermaLink="false">http://www.jetmore.org/john/blog/?p=434</guid>
		<description><![CDATA[Some day I hope to do something with hardware worth writing about in its own right. For now though I am stuck documenting the absurdity of my learning experiences. The only micro-controller stuff I&#8217;ve ever done is with an Arduino. &#8230; <a href="http://www.jetmore.org/john/blog/2012/03/oh-the-things-i-dont-know-avr-isp-edition/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Some day I hope to do something with hardware worth writing about in its own right.  For now though I am stuck documenting the absurdity of my learning experiences.</p>
<p><span id="more-434"></span></p>
<p>The only micro-controller stuff I&#8217;ve ever done is with an Arduino. I would like to change that, so I bought an <a href="http://evilmadscience.com/productsmenu/tinykitlist/447">Alpha Clock Five</a> from <a href="http://evilmadscience.com/">Evil Mad Science</a> to play with. Not only could I learn about AVR programming, I&#8217;ve had an idea since about 2001 for a product that doesn&#8217;t exist which is built around a digital clock. Got the kit and an AVR ISP programmer, built them both, very happy with the clock with its default programming, and the AVR programmer responds to &#8220;pings&#8221; from avrdude.  Woot!</p>
<div id="attachment_435" class="wp-caption aligncenter" style="width: 600px"><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/IMAG0883-s.jpg"><img class="size-large wp-image-435" title="IMAG0883-s" src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/IMAG0883-s-1024x612.jpg" alt="" width="590" height="352" /></a><p class="wp-caption-text">Soda cans not included...</p></div>
<p>But!  I had no idea how to actually program the chip. Hrm. OK, found a couple of pages about building minimalist AVR programming boards (like <a href="http://www.evilmadscientist.com/article.php/avrtargetboards">this one</a>, coincidentally also from EMSL), pretty much confirming what I had been thinking:  A ZIF socket, some perf board, a male header, and some leads. That actually kind of sounded like a fun little thing to put together. Then I started thinking about how it would suck to have to pull the chip out of the clock for each program/test cycle, so maybe I could expand that minimalist board to include full headers for the 40-pin ZIF that could go back to the socket on the clock.  Cool, I could do this!</p>
<p>I&#8217;ve been tossing this around in my head overnight, deciding that it was definitely something I could build, and even sounded like fun. Two things were nagging me though. First, if this is a real problem, why aren&#8217;t there more solutions. Second, why didn&#8217;t EMSL mention this in their otherwise excellent docs. Then a snippet I read last night on what an ISP actually is (In System Programmer) popped up in my head.</p>
<p>I suddenly became 100% sure that there would be an empty 6- or 10-pin header on the back of the clock. I happened to be in the shower right at that moment so it was a bit before I could check, but sure enough, there it was.</p>
<div id="attachment_436" class="wp-caption aligncenter" style="width: 600px"><a href="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/IMAG0885-s.jpg"><img class="size-large wp-image-436" title="IMAG0885-s" src="http://www.jetmore.org/john/blog/wp-content/uploads/2012/03/IMAG0885-s-1024x612.jpg" alt="" width="590" height="352" /></a><p class="wp-caption-text">Why hello there you sexy beast...</p></div>
<p>I went and checked the build docs to see what they called that header.  Sure enough it&#8217;s the &#8220;Six-pin DIL ISP Header&#8221;.  I guess I don&#8217;t need to build the little barebones programmer I had laid out in my head (maybe some future project).</p>
<p>This post has two points.  The first is that I&#8217;m constantly amazed at what I don&#8217;t know.  These kits are put together by people who are striving very hard to make it very easy for beginners to engage with them (I assume they are at least, on the theory that a happy newb is more likely to be a repeat customer) and yet I still almost managed to wander off into the weeds.</p>
<p>The second is this.  That header was the inspiration for <a href="https://plus.google.com/u/0/105818732307605908227/posts/fjRtp1jyKao">this little blurb I posted on Google plus</a> a couple of days ago:</p>
<blockquote><p><em>I have made an important discovery tonight (actually I made it last night, forgot it, and re-discovered it tonight). I think this is a fundamental contribution to hobby electronics, as I haven&#8217;t seen it written down anywhere. Ready?</em></p>
<p><em>If you try the &#8220;tack one post, then press down on the component while re-heating to fully seat&#8221; trick with a male header, you will burn the ever-loving *&amp;^% out of your finger. Those pins go all the way through people.</em></p></blockquote>
<p>This component had actually gotten my attention enough to make me go out of my way to write about it, but it still didn&#8217;t come to mind when I was trying to figure out how to program the darn thing.</p>
<p>But, the upside of all of this is that I get to play with programming my clock tonight without having to order parts for a programmer board!</p>
<p><strong>Updated 2012-03-12</strong>: &#8230;except that I didn&#8217;t get to play with it, because, while the ISP method of programming is technically supported, the provided source code won&#8217;t work with an ISP.  The system also comes with an FTDI interface, and it&#8217;s through this interface that the default firmware is expected to be loaded.  As shipped, the chip is pre-loaded with a slightly modified Arduino environment, and then the default system for the clock is coded against and run via that Arduino environment.  The ISP works, but it would only do low level programming, overwriting the Arduino environment, and clock source has been written against the bare metal.  The only way to load into the Arduino environment, using the default clock code, is via the FTDI interface.  That&#8217;s fine, except I don&#8217;t own a FTDI (USB-TTL) cable.  Reading back through all the docs, this was all stated, I just didn&#8217;t understand what was being stated.  I think the only thing that would have saved me was something like &#8220;Note that there is no example source code available for programming the clock via the ISP interface&#8221; in the ISP section.  I saw that the clock was programmable via the ISP and assumed the example code would work with it.  I have a USB-TTL cable on order, so no big deal, just more examples of not knowing what I don&#8217;t know.</p>
<p>Also amusing in this last vignette is the realization that I still won&#8217;t have been working outside of the Arduino environment.  Oh well, the clock&#8217;s still awesome&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jetmore.org/john/blog/2012/03/oh-the-things-i-dont-know-avr-isp-edition/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

