tag:blogger.com,1999:blog-196510182024-02-28T02:33:58.814-08:00Philosophical GamesDan Fabulich's random technical thoughts.Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.comBlogger24125tag:blogger.com,1999:blog-19651018.post-68254400569638992802012-02-28T08:59:00.000-08:002012-02-28T08:59:31.762-08:00"The Dark Room" YouTube Walkthrough Spoiler Cheat Guide Thingy"You awake to find yourself in a <a href="http://www.youtube.com/watch?v=hvkjP6dqpfY">dark room</a>."<br />
<br />
Here's <a href="http://www.scribd.com/doc/82771999/The-Dark-Room">a map of The Dark Room</a>.<br />
<br />
Note that there are a number of false restarts, where it looks like you've restarted the game, but you're actually in an alternate version of the opening with different actions. <br />
<br />
"Error" is the best fair ending, I think. There's another fine ending at the bottom of the map called "Alright Then."<br />
<br />
But there's a trick to finding the "100% Good Ending." In "Turn Around Redux", which you can reach like this:<br />
<ol><li>Go North<li>Find Light Switch<li>I See<li>Listen<li>Kill Self<li>Hold Breath<li>Pass Time<li>Dig<li>Treasure<li>Turn around</ol>
The sound is backwards. Reverse it using a program like <a href="http://audacity.sourceforge.net/">Audacity</a> to hear the coded message. (Note the comments; try making the last one big.)Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.comtag:blogger.com,1999:blog-19651018.post-44711241019291774762011-06-26T16:22:00.000-07:002011-06-26T16:23:54.729-07:00Style Safari Input Type=Submit ButtonsIn my HTML forms, I like to allow users to submit the form by pressing the Enter key, without JavaScript.
The easiest way to do that is to include an <input type=submit> button.
<pre><form>
<input name="x">
<input type="submit">
</form></pre>
But beware: on Safari (and other WebKit browsers?), <input type=submit> buttons resist CSS styling.
<pre><form>
<input name="x">
<style>
.button {
font-weight: bolder; /* THIS LINE DOES NOTHING IN SAFARI :-( */
}
</style>
<input class="button" type="submit">
</form></pre>
TIL: if you want to force them to be styled, you can use a WebKit proprietary CSS property, -webkit-appearance: none.
<pre><form>
<input name="x">
<style>
.button {
-webkit-appearance: none;
font-weight: bolder;
}
</style>
<input class="button" type="submit">
</form></pre>
If you do that, your buttons will look pretty ugly at first, but with a little work, you can make <a href="http://www.catswhocode.com/blog/top-10-css-buttons-tutorial-list">really cool CSS buttons</a>.Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com0tag:blogger.com,1999:blog-19651018.post-53428812496864358832011-03-06T19:47:00.000-08:002011-03-06T20:20:08.319-08:00Convert PS1 Font from Mac to WindowsIt's not uncommon to receive a font from a designer that works fine on the Mac, but can't be opened on Windows.
<p>This happened to me today. When I viewed the font in the Finder, the font had no filename extension, but Get Info said its Kind was "Postcript Type 1 outline font." Unfortunately, when I zipped the file and opened the zip file on Windows, the file was 0KB long; not very useful!
<p>Here's how I fixed the problem.
<p>To convert it, I used <a href="http://fontforge.sourceforge.net/">FontForge</a>. FontForge is a Unix utility that kinda sorta works on OSX. They don't provide an installer for OSX, so the easiest way to install it is by using <a href="www.macports.org">MacPorts</a>. MacPorts comes with an installer, but you have to install Xcode in order to use it. Xcode can be found on your original OSX install DVDs, on DVD 2. If you can't find those, you can download Xcode for free from Apple, but you have to register as an Apple developer. It's a hassle.
<p>Anyway, assuming you can find and install a copy of Xcode and MacPorts, you should be able to run "sudo port install fontforge" and wait an hour for macports to download a bunch of code from the Internet, compile it, and install it on your machine.
<p>Finally, <i>finally</i>, you can run "fontforge" to open the program. Double-click on your font, and select the File / Generate Fonts ... menu. Select "TrueType" in the list of font types, and save it. It might give you some warnings; I ignored all warnings with no ill effects, but your mileage may vary. This created a .ttf font which worked great on my Windows machine.Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com0tag:blogger.com,1999:blog-19651018.post-50279022389426389112011-01-29T20:50:00.000-08:002011-01-29T21:32:30.796-08:00Play YouTube Videos at Double SpeedI enjoy watching video lectures on YouTube at double speed. (It saves a lot of time!)
<p>Here's how. Download <a href="http://www.videolan.org/vlc/">VLC</a> and run it. Under the File menu, "Open Network ..." and paste in the YouTube URL. Then, under Playback, select Faster or Slower as needed.
<p>Unfortunately, there's a catch: YouTube throttles download speeds. You can only download one second of video per second. So you'll have to wait for the video to finish downloading and then watch it. (Hey, at least you don't have to sit there and watch it download!)
<p>Alternately, you can use VLC's Streaming/Exporting wizard to save the video to disk first.
<p>Have fun!Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com3tag:blogger.com,1999:blog-19651018.post-81102169744627992942010-08-16T13:51:00.000-07:002010-08-16T14:13:46.926-07:00Permanent Windows command-line aliases with doskey and AutoRun<p>On Unix, you can configure command line aliases, like this: "alias lls=ls -l". From then on, running "lls" will automatically run "ls -l", saving you a few keystrokes. The alias will go away whenever you open a command prompt; to make the alias permanent, you can save it in your .profile or .bashrc.
<p>On Windows, you can do the same thing using the <a href="http://technet.microsoft.com/en-us/library/bb490894.aspx">DOSKEY</a> utility to create "macros" like this: "doskey ls=dir". Doskey macros are basically equivalent to Unix aliases (though they have different advanced features).
<p>Automatically loading your Doskey macros is a bit more trouble, however. Doskey allows you to export a macro file, like this: "doskey /macros > my-favorite-macros.txt". You can then import your macro file like this: "doskey /macrofile=my-favorite-macros.txt".
<p>Instead of your .profile, you'll need to configure your <a href="http://technet.microsoft.com/en-us/library/cc779439(WS.10).aspx">AutoRun</a> registry value, in "HKEY_CURRENT_USER\Software\Microsoft\Command Processor". I have my AutoRun set to "c:\dev\autorun.bat" which runs a variety of helpful utilities.
<p>I hope this helps!Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com2tag:blogger.com,1999:blog-19651018.post-5217377826344251712010-05-09T16:46:00.000-07:002010-05-09T19:00:15.882-07:00Customize Android Browser Scaling with target-densityDpi<p>It was hard to find this little gem, so I thought I'd share it with the world: the Android team has implemented a custom meta viewport property to allow you to customize browser scaling for high resolution (HDPI) screens, like the WVGA 480x854 Motorola Droid and 480x800 Nexus One. [This is as opposed to medium resolution (MDPI) HVGA 320x480 T-Mobile G1 or myTouch 3G.]<p>
<p>You can use it like this, for example: <code><meta name="viewport" content="width=device-width, target-densityDpi=device-dpi"></code></p>
<h2>What's Browser Scaling?</h2>
<p>Here, <a href="http://www.quirksmode.org/blog/archives/2010/04/a_pixel_is_not.html">ppk explains why a pixel is not a pixel</a>. The CSS "px" unit may differ from a device's actual pixels, as the browser "scales" images and fonts to a larger size than you requested. (The browser just naturally assumes you didn't really mean "pixels" when you said "px," and helpfully tries to resize your content to make it readable on high-resolution phone screens.)</p>
<p>The Nexus One and Motorola Droid (and any other WVGA devices) will scale pixels even if you use the <code><a href="http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html#//apple_ref/doc/uid/TP40008193-SW6"><meta name="viewport"> tag</a></code> to instruct it not to do this. Worse, they using a non-integer scaling factor (e.g. 1.5x zoom, to scale 320px to 480px) which makes images look really weird.</p>
<h2><code>target-densityDpi</code> to the rescue!</h2>
<p>Use this new undocumented <meta name="viewport"> property: <code><a href="https://android.git.kernel.org/?p=platform/external/webkit.git;a=commit;h=f10585d69aaccf4c1b021df143ee0f08e338cf31">target-densityDpi</a></code>. The only existing documentation is in the <a href="https://android.git.kernel.org/?p=platform/external/webkit.git;a=commit;h=f10585d69aaccf4c1b021df143ee0f08e338cf31">Android check-in comment</a>:
<blockquote><pre>
Add dpi support for WebView.
In the "viewport" meta tag, you can specify "target-densityDpi".
If it is not specified, it uses the default, 160dpi as of today.
Then the 1.0 scale factor specified in the viewport tag means 100%
on G1 and 150% on Sholes. If you set "target-densityDpi" to
"device-dpi", then the 1.0 scale factor means 100% on both G1 and Sholes.
Implemented Safari's window.devicePixelRatio and css media query
device-pixel-ratio.
So if you use "device-dpi" and modify the css for font-size and image
src depending on window.devicePixelRatio, you can get a better page on
Sholes/Passion.
Here is a list of options for "target-densityDpi".
device-dpi: Use the device's native dpi as target dpi.
low-dpi: 120dpi
medium-dpi: 160dpi, which is also the default as of today
high-dpi: 240dpi
<number>: We take any number between 70 and 400 as a valid target dpi.
Fix http://b/issue?id=2071943</pre></blockquote></p>
<h2>So how do I use it?</h2>
<p>Like this, for example: <code><meta name="viewport" content="width=device-width, target-densityDpi=device-dpi"></code></p>
<p>You may also want to add the "user-scalable=no" property to prevent the user from zooming in/out and to guarantee that your images are exactly as you designed them. You can read more about other viewport properties in <a href="http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html#//apple_ref/doc/uid/TP40008193-SW6">Apple's documentation</a> which originally defines theDan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com8tag:blogger.com,1999:blog-19651018.post-9229691530173919702010-04-14T23:57:00.000-07:002010-04-14T23:59:28.459-07:00Choice of Broadsides now available on iPhone, Android, Web<p><a href="http://www.choiceofgames.com/broadsides/">Choice of Broadsides</a>, our newest game, is now available on iPhone, Android and on the web.
<p>"Choice of Broadsides" is a multiple-choice swashbuckling naval adventure, in the spirit of C. S. Forester's Hornblower or Patrick O'Brian’s Aubrey/Maturin books, with a dash of Jane Austen.
<p>We hope you enjoy the app, and we hope that you tell all of your friends about it! Our initial download rate determines our App Store ranking. Basically, the more times you download in the first week, the better we'll rank.
<p>Share and enjoy!Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com0tag:blogger.com,1999:blog-19651018.post-53037696526890349972010-02-02T13:52:00.001-08:002010-02-03T16:55:17.645-08:00The Code Bomb, or: The Newbie with Big Ideas<p>Are you considering making your first big contribution to an open source project? If so, don't make this mistake.</p>
<p>I've been working on open source projects for a number of years now. Sometimes, we've received feedback from potential new developers, saying something like this: "Your project looks great, and I'd love to help out. BUT your code is a mess. I'll help, but only if we XXX."</p>
<p>In this case, "XXX" is an expensive wide-reaching enhancement to infrastructure, something like:</p>
<ul>
<li>Replace the automated build scripts
<li>Refactor the code in a huge way
<li>Add/remove a significant library dependency
<li>Rewrite the code in another language
</ul>
<p>These are very risky infrastructure enhancements: they have a known large up-front cost, an unknown cost in bugs introduced during the enhancement, and unclear benefit, because typically the goal of the enhancement is to do exactly what the code already does, but in a better or more maintainable way.</p>
<p>In many cases it's very tempting to indulge in infrastructure projects like these, and as a result, I'm guilty of implementing my fair share of them myself.</p>
<p>But we have to say "no" most of the time; we're here to build something great, not to <a href="http://sethgodin.typepad.com/seths_blog/2005/03/dont_shave_that.html">shave yaks</a>. Especially since, in most cases, people who say "I'd like to help, but..." don't really want to help anyway; they don't usually stick around long enough to fix the bugs in the new infrastructure they suggested.</p>
<p>UPDATE: Newbie developers tend to want to propose big refactors partly because <a href="http://www.joelonsoftware.com/articles/fog0000000069.html">it's harder to read code than to write it</a>, but also because it's easy to get used to crappy code over time.</p>
<p>In the worst case of this I've ever experienced, a developer "John" (not his real name) required a major refactoring of the code before he'd work on the project, but volunteered to do the entire thing himself. He decided to <a href="http://www.codinghorror.com/blog/archives/001134.html">go dark</a>; he returned, months later, with huge changes to the code, touching almost every file in the system.</p>
<p>In the open source community, we call this a "code bomb." A code bomb is a patch that's so large that no one can review it. Here's <a href="http://blog.red-bean.com/sussman/?p=96">Ben Collins-Sussman on code bombs</a>:</p>
<blockquote>One of the main community "anti-patterns" we’ve talked about is people writing "code bombs". That is, what do you do when somebody shows up to an open source project with a gigantic new feature that took months to write? Who has the time to review thousands of lines of code? What if there was a bad design decision made early in the process — does it even make sense to point it out? Dropping code-bombs on communities is rarely good for the project: the team is either forced to reject it outright, or accept it and deal with a giant opaque blob that is hard to understand, change, or maintain. It moves the project decidedly in one direction without much discussion or consensus.</blockquote>
<p>When a developer (typically a newbie developer) drops a code bomb, it's perfectly fine to just say: <h1>Sorry, we can't accept this patch because it's too large; it's a code bomb!</h1></p>
<p>The onus is on the contributor to break it up into smaller reviewable patches, each of which fixes a clear bug.</p>
<p>In our case, we had to just reject "John's" patch, wholesale. Months of work were simply wasted! <em>Don't let this happen to you!</em></p>
<p>UPDATE: Good commentary on <a href="http://www.reddit.com/r/programming/comments/axal4/the_code_bomb_or_the_newbie_with_big_ideas/">reddit</a></p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com1tag:blogger.com,1999:blog-19651018.post-17615366565442892132010-01-06T23:33:00.000-08:002010-01-06T23:41:32.329-08:00Choice of the Dragon iPhone App Available<p><a href="http://itunes.apple.com/us/app/choice-of-the-dragon/id348940932?mt=8"><img src="http://www.choiceofgames.com/blog/wp-content/uploads/2010/01/appstore.png" alt="Available on the App Store" title="App Store" width="260" height="90" class="alignleft size-full wp-image-32" /></a></p>
<p><a href="http://www.choiceofgames.com/dragon/">Choice of the Dragon</a> is now available as an <a href="http://itunes.apple.com/us/app/choice-of-the-dragon/id348940932?mt=8">iPhone app</a>! Please download and review it.</p>
<p>Coming soon: Facebook integration. <a href="http://spreadsheets.google.com/viewform?formkey=dDFMNDk1VHF3MGEzbFJyZFNaSHp4Q1E6MA">Sign up for other mobile versions</a> here.</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com1tag:blogger.com,1999:blog-19651018.post-50826089394648715252010-01-03T14:11:00.000-08:002010-01-03T14:12:35.589-08:00Choice of the Dragon Android app available<p><img class="alignright size-full wp-image-10" title="The Droid" src="http://www.choiceofgames.com/blog/wp-content/uploads/2010/01/droid.png" alt="The Droid" width="76" height="90" /> <a href="http://www.choiceofgames.com/dragon/">Choice of the Dragon</a> is now available as an <a href="market://search?q=pname:com.choiceofgames.dragon">Android app</a>! Please download and review it.</p>
<p>(And if you haven't played the web version yet, well, <a href="http://www.choiceofgames.com/dragon/">what are you waiting for?</a>)</p>
<p>iPhone and other smartphone versions are coming soon. <a href="http://spreadsheets.google.com/viewform?formkey=dDFMNDk1VHF3MGEzbFJyZFNaSHp4Q1E6MA">Sign up for other mobile versions</a></p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com0tag:blogger.com,1999:blog-19651018.post-70600192925405141932009-12-20T15:33:00.000-08:002009-12-20T15:35:22.496-08:00Announcing: Choice of the DragonI've been working on this game for the past few months:<blockquote><p><a href="http://www.choiceofgames.com/dragon/">Choice of the Dragon</a></p>
Play as a fire-breathing dragon who sleeps on gold and kidnaps princesses for fun.</blockquote>In the course of developing the game, I designed a programming language called "ChoiceScript" specifically for multiple choice games; I also wrote a bunch of text for the game, along with my friend Adam (with help from Kevin).
Please share this with friends!Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com1tag:blogger.com,1999:blog-19651018.post-48221064640454470242009-08-22T12:46:00.000-07:002010-01-05T13:09:09.790-08:00StumbleUpon Doesn't Honor Permanent Redirects<p>The folks at StumbleUpon just gave me the worst website advice that I have ever heard.</p>
<p>When you change your domain name, StumbleUpon refuses to follow the permanent redirect to your new site. They recommend leaving the old website up with a "please click here" message instead!</p>
<h2>Wait, really?</h2>
<p>StumbleUpon provides a browser toolbar with a "Stumble!" button. When you click the button, it sends you to a random web page, and lets you rate it. If lots of people like your page, StumbleUpon will send more people there.</p>
<p>I've been running an online game from my personal website for about eight years. It eventually got pretty popular, averaging ~2 million pageviews a month. The load became too much for my home DSL connection, so about a month ago, I decided to move the game to Google AppEngine, on a separate domain. I diligently set up a permanent (301) redirect from the old website to the new website, and notified Google of my address change using Google Webmaster Tools.</p>
<p>For a while, everything was fine, but then, all of a sudden, I noticed my traffic had dropped by 50%. Puzzled, I checked my logs.</p>
<p>It turns out that about 50% of my traffic came from StumbleUpon; they were sending me hundreds of new visitors a day, some of whom would play the game for hours at a time. The game is a lot of fun, so it got hundreds of positive reviews. Eventually, StumbleUpon grew to represent a huge fraction of my inbound traffic. Now, all of it is gone.</p>
<p>StumbleUpon now says, "<a href="http://www.stumbleupon.com/url/www.theblackforge.net">We are showing this site as unavailable. Should we check again?</a>"</p>
<p><i>UPDATE (Jan 5 2010): StumbleUpon has removed this warning message from their site, but they're still not sending traffic to the redirected URL.</i></p>
<p>I emailed StumbleUpon about this, and they gave me this advice:</p>
<blockquote><pre>Hello Dan,
Thanks for your feedback,
Unfortunately, once you've redirected a site, you
are losing all reviews and traffic from the
original URL. There is nothing we can do about
this, as each unique URL in our community has it's
own unique review page.
If we allowed transfer of reviews and traffic
counts, we would facilitate gaming of our system.
You may however do a different type of redirect:
keep the homepage of the old URL alive and insert
a link that users must click to reach the new URL.
That way, the review page for the old URL will
still be accessible and the site will still regain
it's viral momentum and get stumbled around. While
doing this, you can slowly build reviews and
traffic for the new URL.
I hope this helped.
Regards,
Monica
xxxx@stumbleupon.com</pre></blockquote>
<p>This is horrible advice; nobody should ever do this.</p>
<p>If you follow Monica's advice, search engines like Google and Bing will continue to send searchers to the old website instead of the new website. Both Google and Bing rank websites by how many people link to them. Since the old website has more links, it will rank higher in the search results than the new website. Eventually, hopefully, you'll have two websites in Google and Bing, each performing only half as well as they should.</p>
<p>This is bad for my users and bad for searchers. Nobody wants to see duplicate content in their search results.</p>
<h2>Gaming the system?</h2>
<p>Monica says that if StumbleUpon honored redirects, it could be used to "game" their system. After all, what if I one day replaced my website with a redirect to a spam website? Then StumbleUpon might send users to some spam site.</p>
<p>But that's silly. Anyone who could redirect my website to spam could just as easily put spam right on my website! Redirecting doesn't help me game their system at all.</p>
<p>On the contrary, instead they're asking me to turn my old site into a pointless "doorway" page, forcing their users to click on an extra link just to get to the fun stuff. How is that good for their users?</p>
<p>If you don't believe me, then maybe you'll believe Google. StumbleUpon may claim to be worried about spam, but Google eats <em>far</em> more spam than StumbleUpon. People try to spam Google all the time, often by setting up useful sites and then replacing them with spam sites. If redirects made it easier to "game the system", then Google would stop honoring them.</p>
<p>Instead, Google gives clear and explicit directions explaining <a href="http://www.google.com/support/webmasters/bin/answer.py?answer=83105">how to change your domain name</a>. They recommend setting up a permanent 301 redirect, and notifying Google of the change of ownership using Google Webmaster Tools.</p>
<p>StumbleUpon should learn a lesson from Google and honor permanent redirects. It's the right thing to do for my users, Google's users, and their users.</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com1tag:blogger.com,1999:blog-19651018.post-15448601241077889012009-04-26T12:13:00.000-07:002009-04-26T12:44:34.888-07:00Tipjoy is Insecure; Don't Use It<p>Tipjoy is a cool new site that lets you exchange micropayments over the Internet. Unfortunately, on their "createAccount" page, they prompt you to give them your password without encryption!</p>
<p><a href="http://tipjoy.com/createAccount/">http://tipjoy.com/createAccount/</a></p>
<p>That URL should be HTTPS-only, not HTTP. If you enter your password on that page, any "man in the middle" can read it and use it to impersonate you on Tipjoy.</p>
<p>By capturing your password, the attacker can spend money that belongs to you, transfer money stored in your Tipjoy account into the attacker's account, etc.</p>
<p>Bizarrely, tipjoy.com does support HTTPS... they just choose not to use it on most of their webpages, including the /createAccount page and the /settings/account/ page where you go to change your password. (The /login page is HTTPS by default.)</p>
<p>You can even opt-in to transmit your password securely, by modifying the URL to use SSL: <a href="https://tipjoy.com/createAccount/">https://tipjoy.com/createAccount/</a>. That may be an acceptable workaround until tipjoy fixes their site.</p>
<p>But, even knowing that workaround, you STILL shouldn't use Tipjoy to put a button on your site until they fix this issue. If you put a Tipjoy button on your site, your users (the people who like you and generously want to give you a small tip) will probably NOT notice the problem; they will just create an account using the default /createAccount link, exposing their passwords to a man-in-the-middle attack.</p>
<p>Hopefully Tipjoy will get the message and fix this soon. Frankly, as a payment exchange system, their ENTIRE SITE should be behind HTTPS, not just key login pages. This is how paypal.com works; it's also how most bank websites work. When money is on the line, you really can't accept anything less.</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com5tag:blogger.com,1999:blog-19651018.post-17483088122673245492008-11-01T17:38:00.000-07:002008-11-01T17:59:25.668-07:00SPOILER: Unlock Blue Frog "Cheat" Mode and Battle Mode in Quest For Glory 2 (QFG2) VGA Remake<h2>What is Blue Frog Mode?</h2>
<p>The original Sierra "Quest for Glory 2: Trial by Fire" EGA game had a "cheat" mode. If you typed "suck blue frog," you could use secret key codes to give you unlimited money, raise your stats, create <a href="http://www.theblackforge.net/qfg2/items.txt">any item</a>, teleport to <a href="http://www.theblackforge.net/qfg2/rooms.txt">any room</a> in the game, etc. Officially it's a debug mode, used by developers to test the game without playing through the entire game. Unofficially, most people use it to cheat.</p>
<p>The AGDI have released a VGA remake for Quest for Glory 2; it has a similar "debug" / "cheat" mode called "Blue Frog Mode." Finding it is hard, but unlocking it is easy.</p>
<p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfmmkTU2z2L0KSvVt7lD7MOh2LwbwHHRmAOaDop-s9i5g3PhuRby4cclhjAXmIzvXlJQmIHWrobDbgoSjQKEgca0Ih7Lt2IIPnSRTT9b6N0UOFMmH7rkVgPnCWzq_9YPGSgHlJ/s1600-h/blue-frog-menu.png"><img style="cursor:pointer; cursor:hand;width: 200px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfmmkTU2z2L0KSvVt7lD7MOh2LwbwHHRmAOaDop-s9i5g3PhuRby4cclhjAXmIzvXlJQmIHWrobDbgoSjQKEgca0Ih7Lt2IIPnSRTT9b6N0UOFMmH7rkVgPnCWzq_9YPGSgHlJ/s200/blue-frog-menu.png" border="0" alt="Blue Frog Mode Menu" id="BLOGGER_PHOTO_ID_5263855077456055794" /></a></p>
<h2>Unlocking the easy way</h2>
<p>Just download this file (<a href="http://www.theblackforge.net/qfg2/Qfg2vga.025">Qfg2vga.025</a>) put it in your "Quest for Glory II" installation directory, which is probably in "C:\Program Files\AGD Interactive\Quest for Glory II." You'll need to replace the Qfg2vga.025 file that already exists in that directory.</p>
<p>Then just start the game from the beginning (or import a character from the previous game, "Quest for Glory I" aka Qfg1 aka "Hero's Quest"). Press F10 to activate Blue Frog Mode.</p>
<h2>Unlocking the normal way</h2>
<p>It's a little more work to unlock Blue Frog Mode yourself without "cheating." You have to find and capture the blue frog, and beat the game with maximum puzzle points.</p>
<p>First, you'll need the bellows from Issur, the weaponsmaster. Go to the Dervish; there's a lilypad in Dervish's oasis. Repeatedly exit/enter the oasis. Every time you enter the oasis there's a small chance that a tiny blue frog will be sitting on the lilypad. Use the bellows on the frog; you'll take the frog and keep it in your inventory.</p>
<p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH50wn8EQZgRvKkMx4HVkbvIMRhZDrgFF9IemHBR-S14HMN9kkIbL3Frl_oWSTUin6IrzMBhLSIJUF4JGEyxVfIxJAAVJH5MDBVh_VRf7f0l9egC047RhfJ944nTEpHpUt6N7m/s1600-h/blue-frog-oasis.png"><img style="cursor:pointer; cursor:hand;width: 200px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH50wn8EQZgRvKkMx4HVkbvIMRhZDrgFF9IemHBR-S14HMN9kkIbL3Frl_oWSTUin6IrzMBhLSIJUF4JGEyxVfIxJAAVJH5MDBVh_VRf7f0l9egC047RhfJ944nTEpHpUt6N7m/s200/blue-frog-oasis.png" border="0" alt="Dervish Oasis with Blue Frog" id="BLOGGER_PHOTO_ID_5263855786107330946" /></a></p>
<p>Note that the bellows must be empty to find and capture the blue frog. Later in the game you have to use the bellows to capture the Air Elemental; once you've captured the Air Elemental, you can't get the blue frog.</p>
<p>Once you have the blue frog, you must finish the game with maximum puzzle points to unlock Blue Frog Mode. For non-Paladins, you need 500 puzzle points out of a maximum of 500; for Paladins, you need 550 puzzle points out of a maximum of 550. (If you're short a few Paladin points at the end of the game, you can choose to "decline" becoming a Paladin to lower the maximum to 500 points.)</p>
<p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDG5YeR2GwWwelVtDerJAmOm_g1z-B9y4xHIEpFTBbaT-zufRAAJ7yKj3GyqrntCYO-dWOBJC7IeYaush1B9tyuMu1pGZoJDg9fjr9nDs9N_YFSkUsroKlDxvd3qlrXfmKg_mJ/s1600-h/blue-frog-mode-unlocked.png"><img style="cursor:pointer; cursor:hand;width: 200px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDG5YeR2GwWwelVtDerJAmOm_g1z-B9y4xHIEpFTBbaT-zufRAAJ7yKj3GyqrntCYO-dWOBJC7IeYaush1B9tyuMu1pGZoJDg9fjr9nDs9N_YFSkUsroKlDxvd3qlrXfmKg_mJ/s200/blue-frog-mode-unlocked.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5263855788166622962" /></a></p>
<p>Note that on the AGDI forums, some people have suggested that you also need to max out all of your character statistics (strength, agility, etc. need to be 200) to unlock Blue Frog Mode. You definitely do not need max stats. I was able to unlock Blue Frog Mode as a Thief imported from Qfg1 with Communications of 185 and Honor of 107. Admittedly, all of my other stats were at or above 200, so there may be some requirement around stats, but I doubt it.</p>
<p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5jRoj7Euemut01xeDyX7X9WosmwY9XrS67mJf4Qc7oVX9FWHPJltEPiL6O753KX7F8bVab-1TfTO3CoI8dvasdHMmeHqyPZjbBpjhqzMcuaRpK38utIjVoBZ941c3zTZtabd-/s1600-h/non-perfect-stats.png"><img style="cursor:pointer; cursor:hand;width: 200px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5jRoj7Euemut01xeDyX7X9WosmwY9XrS67mJf4Qc7oVX9FWHPJltEPiL6O753KX7F8bVab-1TfTO3CoI8dvasdHMmeHqyPZjbBpjhqzMcuaRpK38utIjVoBZ941c3zTZtabd-/s200/non-perfect-stats.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5263855788845530194" /></a></p>
<p>Instructions on how to get the bellows and find the Dervish are available in <a href="http://www.gamefaqs.com/computer/doswin/file/952659/53981">CyricZ's excellent GameFAQs walkthrough for the VGA remake</a>; no doubt it will be updated soon to include information on how to unlock Blue Frog Mode. If you're having trouble finishing the game with maximum puzzle points, CyricZ also includes a "Point List" in section 10C, which lists all of the possible ways to earn points. In some cases, you may need to restore your game or even start your game over from scratch to get maximum points.</p>
<h3>Item/Room List</h3>
<p>Here's a <a href="http://www.theblackforge.net/qfg2/items.txt">list of item numbers for Blue Frog Mode</a>; here's a <a href="http://www.theblackforge.net/qfg2/rooms.txt">list of room numbers for Blue Frog Mode</a>. Note that most room numbers just cause the game to crash with an error message.</p>
<h2>Battle Mode</h2>
<p>There is a second "debug" mode available called "Battle Mode." Battle Mode and Blue Frog Mode must be unlocked separately.</p>
<p>Battle Mode has five features:
<ul>
<li>Combat Tests: Battle any specific monster in "test" mode. In test mode, you won't die if you lose, but you also won't gain stats during the fight. (Not that you care because you've also got Blue Frog Mode, right?)
<li>Damage Meter: See the exact amount of damage dealt in combat.
<li>Summon Monsters: In the desert, cause a specific monster to appear.
<li>Enable/Disable Random Encounters in the desert.
<li>Set Battle Speed: Press Ctrl-Z during combat to slow down or speed up the fight.
<li>Enable/Disable Auto-Critical Mode: All of your attacks become critical hits.
</ul></p>
<p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDoq-ZTT1B4ncLPnla-zhSW04YrM5KEpqHnNdGkLU7EVY6T6SmqQ80jX54GpYxOmLufO5eLfCckqBkBkNZOSKOOl0beFm-KN4OdKCiT89oGOuHeS479l6KM211NGey59Asth8t/s1600-h/combat-tests.png"><img style="cursor:pointer; cursor:hand;width: 200px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDoq-ZTT1B4ncLPnla-zhSW04YrM5KEpqHnNdGkLU7EVY6T6SmqQ80jX54GpYxOmLufO5eLfCckqBkBkNZOSKOOl0beFm-KN4OdKCiT89oGOuHeS479l6KM211NGey59Asth8t/s200/combat-tests.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5263855792722145058" /></a>
</p>
<p>The easy way to unlock Battle Mode is to install this file (<a href="http://www.theblackforge.net/qfg2/Qfg2vga.026">Qfg2vga.026</a>) in your Qfg2 installation directory. Start a new game and press F4 to access the Battle Mode menu.</p>
<p>The hard way to unlock Battle Mode is to find, defeat and capture the secret Pizza Elemental, then beat the game with maximum puzzle points.</p>
<p>To capture the Pizza Elemental, you'll need an empty pizza box. You can find one outside the entrance to the Eternal Order of Fighters. In the simplified street system it's at the end of Saif Darb; in the classic street system it's at the end of Askari Darb, which branches off of Saif Darb.</p>
<table border=1><tr><td><center><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzNVTW4BW-cR4o1woUgJfuxGsioqapIzM7A7jc6pHO-1nkcEEyJPEg67Zgt7sLELVjvM6ot_itjyKqRfZ__0aKs3gdM7yCbJS_AkqqNQatttI-P3x2NhrU4_fbJR8JOE5Pi9j2/s1600-h/pizza-box.png"><img style="cursor:pointer; cursor:hand;width: 200px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzNVTW4BW-cR4o1woUgJfuxGsioqapIzM7A7jc6pHO-1nkcEEyJPEg67Zgt7sLELVjvM6ot_itjyKqRfZ__0aKs3gdM7yCbJS_AkqqNQatttI-P3x2NhrU4_fbJR8JOE5Pi9j2/s200/pizza-box.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5263855795824200946" /></a>
<br>Simplified</center></td><td><center><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLsP36h-zrCqJSxObxGLZzHpxDot4Fp2WidlckS4BjYswddDEd4CE007zb-nADTlBF3irKOcem4EpEZ8143OumGJeWHIaA1cqVVrt_e9Pax9r-92_a35aPW2G83OAPILDRlCpl/s1600-h/pizza-box-classic.png"><img style="cursor:pointer; cursor:hand;width: 200px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLsP36h-zrCqJSxObxGLZzHpxDot4Fp2WidlckS4BjYswddDEd4CE007zb-nADTlBF3irKOcem4EpEZ8143OumGJeWHIaA1cqVVrt_e9Pax9r-92_a35aPW2G83OAPILDRlCpl/s200/pizza-box-classic.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5263855907922934162" /></a>
<br>Classic</center></td></tr></table>
<p>You can't find the Pizza Elemental until after you've defeated the Fire Elemental, on Day 5 or later. When you're ready, go four (4) screens South of Shapier, then about forty (40) screens West. Eventually you'll find the Pizza Elemental.</p>
<p>Defeating the Pizza Elemental is a lot of work, even at the easiest combat difficulty setting. I recommend unlocking Blue Frog Mode first, and using it to raise your Luck, Dodge and Weapon Use skill to 250 before challenging him. (You can raise your other stats to 250 as well, but if you start the fight with more than 200 HP or 200 SP, you'll be reduced to 200 HP/SP at the start of the fight.)</p>
<p><a href="http://www.gamefaqs.com/computer/doswin/file/952659/53981">CyricZ's walkthrough</a> includes some tips on how to defeat the Pizza Elemental. There's also a <a href="http://www.youtube.com/watch?v=HKuyj_jfc60">YouTube clip demonstrating how to beat him</a>.</p>
<p>Basically, you have to step back and duck a lot (avoid jumping, it wastes stamina). Be sure to attack the elemental when he eats you or tries to eat a pizza. Eventually you'll run out of stamina; just patiently dodge the falling pizzas to recover stamina until you're ready to fight again. It requires quick reflexes and a lot of patience.</p>
<p>Once you've beaten the Pizza Elemental, use your pizza box to capture him. Then beat the game with maximum puzzle points (again, maximum stats are not required) to unlock Battle Mode.</p>
<p>Despite what you may have read elsewhere, you do not have to defeat Sweeping Sir James to unlock Battle Mode, or complete the Warrior's Diary. (See <a href="http://www.gamefaqs.com/computer/doswin/file/952659/53981">CyricZ's walkthrough</a> for details on these optional missions.)</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.comtag:blogger.com,1999:blog-19651018.post-82742307482081095132008-03-10T22:34:00.000-07:002008-03-10T22:59:57.982-07:00Java annotation parameters can't default to null<p>I don't update here often enough, but here's a tidbit I wish I'd found on Google earlier.</p>
<p>Suppose you want to write a Java annotation that has a parameter value, but you want its default value to be <code>null</code>. Well, too bad. It is illegal to write this annotation:</p>
<pre>
public @interface Optional {
public String value() default null;
}
</pre>
<p>It's a compile time error. javac says "attribute value must be constant;" Eclipse says "The value for annotation attribute Optional.value must be a constant expression." In fact, it's not that surprising, because even if you didn't set a default value, writing this would also be illegal (same error):</p>
<pre>
@Optional(null)
</pre>
<p>What the error is saying is that you can't set a Java annotation parameter to <code>null</code>.</p>
<p>Why is this? The specifications are opaque. <a href="http://jcp.org/aboutJava/communityprocess/review/jsr175/index.html">JSR-175</a>, which defined annotations for Java 5, just says "If member type is a primitive type or String, the ConditionalExpression must be a constant expression (<a href="http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.28">JLS 15.28</a>)." JLS 15.28, in turn, says that constant expressions can be, for example, any of these:</p>
<pre>
true
(short)(1*2*3*4*5*6)
Integer.MAX_VALUE / 2
2.0 * Math.PI
"The integer " + Long.MAX_VALUE + " is mighty big."
</pre>
<p>Notice anything missing? That's right, <code>null</code>. You can never pass <code>null</code> as a Java annotation parameter value, because, uh, <code>null</code> isn't a ConstantExpression.</p>
<p>Why? We may never know. The only thing you can do is workaround it, like this:</p>
<pre>
public @interface Optional {
public String value() default NULL;
public static final NULL = "THIS IS A SPECIAL NULL VALUE - DO NOT USE";
}
</pre>
<p>... and then make your code carefully treat Optional.NULL as if it were really <code>null</code>.</p>
<p>LAME!</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com8tag:blogger.com,1999:blog-19651018.post-28022409081068128222008-01-11T11:32:00.000-08:002008-01-11T11:34:11.517-08:00BART Tracker<p><a href="http://bart-tracker.sourceforge.net/">BART Tracker</a> reports on arriving San Francisco area BART trains in real time, using a system tray icon.</p>
<p><img src="http://sourceforge.net/dbimage.php?id=155349" ></img></p>
<p>It uses BART's new "<a href="http://bart.gov/stations/eta/">Estimated Arrivals</a>" or "BART System Status" feature, that reports on the current up-to-the-minute status of arriving trains at every BART station.</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com0tag:blogger.com,1999:blog-19651018.post-10922563761291537422007-10-18T14:45:00.001-07:002007-10-18T14:59:27.036-07:00Hudsuckr: Advanced Windows proxy configuration from the command line<p>Windows stores proxy configuration settings in the registry, in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings. However, the settings there like "ProxyServer" "ProxyEnable" can be somewhat misleading, and certain advanced settings (like the setting to "Automatically detect settings" using WPAD) aren't available as separate registry keys.</p>
<p>In IE4, proxy settings were stored in the "Internet Settings" key as simple strings (REG_SZ) and ints (REG_DWORD), but since IE5 it has been possible to set proxy configurations separately for each Internet connection. For example, you could set up your computer to use a proxy when you're using a VPN, use WPAD when you're on dial-up, and to go directly to the Internet when you're corrected directly using the LAN. These "per connections" settings are stored in the "Internet Settings\Connections" key; unfortunately, they're stored there in REG_BINARY blobs that are not meant to be edited directly. (When you use the "Internet Options" Control Panel, it automatically updates both the REG_BINARY blobs and the regular registry settings in "Internet Settings" automatically.)</p>
<p>That makes it difficult to configure "per connection" settings (like WPAD) using command line tools, including proxycfg.exe and others. We needed to be able to automatically enable/disable WPAD in Selenium RC, so I've coded up a simple command-line executable called "hudsuckr" that lets you do it. (It's named after the classic 1994 Coen brothers movie <i><a href="http://www.imdb.com/title/tt0110074/">The Hudsucker Proxy</a></i>. "Hudsuckr" contains no "e" because that makes it easier to Google and because it makes it more Web 2.0. ;-)</p>
<p>Here's the usage documentation that you get when you run "hudsuckr --help" from the command line:</p>
<pre>
Hudsuckr Windows Proxy Configuration Tool: "You know, for kids!"
Windows manages Internet proxy connection information in the registry;
each Internet "connection" can have its own separate proxy
configuration. These settings correspond to settings in the "Internet
Options" Control Panel, under the "Connections" tab.
Run "hudsuckr" without arguments to print out the current proxy
configuration details. We print out the name of the current active
connection, the four connection flags (DIRECT, PROXY, AUTO_PROXY_URL,
AUTO_DETECT), and three strings: PROXY_SERVER, PROXY_BYPASS, and
AUTOCONFIG_URL. The seven settings are described in MS documentation
available here: <a href="http://msdn2.microsoft.com/en-us/library/aa385145.aspx">http://msdn2.microsoft.com/en-us/library/aa385145.aspx</a>
Run "hudsuckr" with exactly eight arguments to set the proxy
configuration, like this:
hudsuckr (null) true true true true "localhost:4444" "<local>" "file://c:/proxy.pac"
Specify the name of the connection first (or use the LAN settings by
specifying "(null)"), then set the four flags using "true" and
"false", then the proxy server (with a colon to specify the port), the
list of servers to bypass delimited by semi-colons (with "<local>" as
a special string that bypasses local addresses), and finally the URL
to a proxy PAC autoconfiguration file. Use "" or "(null)" to leave
string settings blank/empty.
If you're still confused about the flags, look at the proxy settings
in the "Internet Options" Control Panel. See how you can check those
checkboxes independently of one another? The flags correspond to those
checkboxes. If AUTO_DETECT is true, IE will try to use WPAD; if
successful, WPAD will override the specified AUTOCONFIG_URL. If an
AUTOCONFIG_URL is detected (by AUTO_DETECT) or specified directly
(AUTO_PROXY_URL is enabled), IE will use the autoconfig script as a
proxy PAC file. If no AUTOCONFIG_URL was specified or detected, IE
will attempt to use the server specified in PROXY_SERVER if the PROXY
flag is enabled; it will bypass the proxy for the list of servers
specified in PROXY_BYPASS. Finally, if PROXY, AUTO_DETECT and
AUTO_PROXY_URL are all set to false, IE will attempt to contact the
web server directly. Note that the DIRECT flag always appears to be
true, even if the PROXY flag is true; we recommend you leave it that
way, too.
</pre>
<p>Here's what you might see if you run hudsuckr without command line arguments:</p>
<pre>
> hudsuckr.exe
ACTIVE_CONNECTION=(null)
PROXY_TYPE_DIRECT=true
PROXY_TYPE_PROXY=false
PROXY_TYPE_AUTO_PROXY_URL=false
PROXY_TYPE_AUTO_DETECT=false
INTERNET_PER_CONN_PROXY_SERVER=(null)
INTERNET_PER_CONN_PROXY_BYPASS=(null)
INTERNET_PER_CONN_AUTOCONFIG_URL=(null)
</pre>
<p>Or, after you've configured lots of proxy settings:</p>
<pre>
> hudsuckr.exe (null) true true true true "localhost:4444" "<local>" "file://c:/proxy.pac"
ACTIVE_CONNECTION=(null)
PROXY_TYPE_DIRECT=true
PROXY_TYPE_PROXY=true
PROXY_TYPE_AUTO_PROXY_URL=true
PROXY_TYPE_AUTO_DETECT=true
INTERNET_PER_CONN_PROXY_SERVER=localhost:4444
INTERNET_PER_CONN_PROXY_BYPASS=<local>
INTERNET_PER_CONN_AUTOCONFIG_URL=file://c:/proxy.pac
</pre>
<p>To turn off all proxies for the LAN connection, you might run a command like this:</p>
<pre>
> hudsuckr (null) true false false false (null) (null) (null)
ACTIVE_CONNECTION=(null)
PROXY_TYPE_DIRECT=true
PROXY_TYPE_PROXY=false
PROXY_TYPE_AUTO_PROXY_URL=false
PROXY_TYPE_AUTO_DETECT=false
INTERNET_PER_CONN_PROXY_SERVER=(null)
INTERNET_PER_CONN_PROXY_BYPASS=(null)
INTERNET_PER_CONN_AUTOCONFIG_URL=(null)
</pre>
<p><a href="http://svn.openqa.org/svn/selenium-rc/trunk/server-coreless/src/main/resources/hudsuckr/hudsuckr.exe">Download hudsuckr.exe</a><br />
<a href="http://svn.openqa.org/svn/selenium-rc/trunk/hudsuckr/">Hudsuckr source code</a></p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com3tag:blogger.com,1999:blog-19651018.post-84810072170324584442007-09-07T22:22:00.000-07:002007-09-07T22:24:47.605-07:00Windows, killableprocess, and the Case of the Suicidal Parent (or: How to kill a process and all of its subprocesses reliably on Windows)<p>The two major browsers available on the market today (Firefox and IE) are sometimes "suicidal parents" when launched from the command line. That is, they occasionally have to re-launch themselves, by spawning a new copy of their executable (firefox.exe or iexplore.exe) and killing the currently running process. In these cases, the orphaned child process lives on to become the GUI that you actually see on the screen.</p>
<p>Firefox commits suicide when it sees that it needs to make significant adjustments to its user profile directory, e.g. when it has detected new versions of your extensions/add-ons. IE commits suicide on Vista when running in Vista's new "Protected" mode. When you launch iexplore.exe from the command line on Vista in Protected mode, your iexplore.exe process spawns a copy of "ieuser.exe" (if one isn't running already) and allows THAT process to launch a new copy of iexplore.exe.</p>
<p>This suicidal behavior is invisible to normal end users, but it can be very difficult for people who want to automatically launch and kill browsers for automated testing, like I do. For example, if you spawn a suicidal parent in Java and then try to .destroy it, you'll run straight into Java bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092">4770092</a> (Process.destroy does not kill multiple child processes).</p>
<p>Benjamin Smedberg posted an interesting blog entry about a way to use Python to handle child processes: <a href="http://benjamin.smedbergs.us/blog/2006-12-11/killableprocesspy/">killableprocess.py</a>. On Windows, killableprocess.py handles child processes by launching them inside of a Windows "Job" object. Processes within a Job may be assigned special security privileges or restrictions, and, by default, whenever any process in a Job creates a subprocess, the subprocess becomes a member of the same Job. (Job objects were introduced in Windows 2000.) It's easy and convenient to terminate an entire Job at once, killing all processes within it, even if the processes are orphans of suicidal parents.</p>
<p>Using Jobs to manage a process tree is a great idea, but not everybody in the world has the Python runtime installed, so I decided to code up something like it in C++.</p>
<p>The compiled executable (killableprocess.exe) can be used to launch any other process you like, assigning that process to a new unnamed Job. killableprocess.exe will automatically terminate if every process in the Job has died. It's also designed to handle shutdown signals (Ctrl-C and Ctrl-Break), to terminate the Job as killableprocess.exe is being shutdown. As a fallback, it additionally registers a "job limit" with the operating system to automatically kill the Job if killableprocess.exe is forcibly terminated. (Beware... the <a href="http://jira.openqa.org/browse/SRC-343">job limit doesn't work if you use Process Explorer</a>.)</p>
<p>You can use killableprocess.exe as a wrapper around other processes that you expect to be suicidal parents.</p>
<p><a href="http://svn.openqa.org/svn/selenium-rc/trunk/server-coreless/src/main/resources/killableprocess/killableprocess.exe">Download killableprocess.exe</a><br />
<a href="http://svn.openqa.org/svn/selenium-rc/trunk/killableprocess/">Source code for killableprocess.exe</a></p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com2tag:blogger.com,1999:blog-19651018.post-84428105352315105802007-05-08T22:36:00.000-07:002007-05-08T23:33:48.399-07:00"Buildable" JDK doesn't build on Windows: missing t2k.lib<p>What a mess.</p>
<p>This morning at JavaOne, Rich Green announced that you could at long last build a working JDK/JRE from GPL'd source... mostly. A few key libraries remained non-GPL'd (encumbered) so they had to be shipped as binary plugs. That's fine... we can work on that. If we can build the code, that is.</p>
<p>But if you go to <a href="http://openjdk.java.net">openjdk.java.net</a> right now, and go download openjdk-7-ea-src-b12-06_may_2007.zip and jdk-7-ea-plug-b12-windows-i586-06_may_2007.jar, you'll find that the JDK won't build correctly on Windows. Instead, <a href="http://mail.openjdk.java.net/pipermail/build-dev/2007-May/000000.html">you'll get this error message</a> when you run "make sanity":</p>
<code><pre>ERROR: Can't locate t2k import library.
Please check your access to
/c/devtools/jdk-7-binary-plugs/jre/bin/t2k.lib
and/or check your value of ALT_CLOSED_LIB_DIR.</pre></code>
<p>That file is indeed missing from the Windows binary plugs. We have a t2k.dll, t2k.pdb, and t2k.map, but no t2k.lib. At JavaOne today Dmitri Trembovetski confirmed that this was a real issue, and that t2k.lib is a core part of the graphics rasterizer, one of the key encumbered components of the binary plugs.</p>
<p>Dmitri also suggested a workaround, which I'm not too excited about but which would probably work: you could, if you wanted, go get the JRL JDK 7,
accept the JRL, and build a t2k.lib from <i>that</i> source. I'm concerned about my contaminating the new GPL'd Java by doing that, so I think I don't want
to do that. Instead, someone at Sun should probably release another version of the binary plugs archive with the necessary file(s) included.</p>
<p>Dmitri and Tom Marble suggested that I file a bug on bugs.sun.com, but bugs filed there aren't immediately visible via the web. I was not even assigned a bug number, so I can't reference it here. And, unfortunately, even though bugs.sun.com HAS a "build" category for J2SE bugs, ordinary users can't file bugs against it. I had to file the bug against "installation" instead.</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com0tag:blogger.com,1999:blog-19651018.post-1166775871359356262006-12-22T00:23:00.000-08:002006-12-22T00:24:31.370-08:00JavaScript Support in Java 6 is WORSE than old Rhino 1.6Java 6 is out, and with it, it includes built-in support for the Rhino JavaScript interpreter, warts and all. I've had a lot of problems with the Rhino JS interpreter, but astoundingly, the Rhino built-in to Java 6 is actually <i>worse</i> than the Rhino you can download from mozilla.org.<p></p>
<p>How, you ask?</p>
<h3>Exceptions don't work</h3>
<p>Well, first, it's got all of the problems you'll find in standard Rhino. The most annoying problem you'll encounter is that exceptions don't work. Here, eval this (in either Rhino 1.6R5 or Java 6):</p>
<p><code>try {
throw new Error("urk");
} catch (e) {
throw e;
}</code></p>
<p>To eval this in Rhino, put this text in a file called "bug.js", run Rhino with "java -jar js.jar" and type "load('bug.js')".</p>
<p> <code>"bug.js", line 4: exception from uncaught JavaScript throw: [object Error]</code></p>
<p>There's two things very wrong with this, one more serious than the other. The first thing wrong with it is that it's reporting the error on the wrong line number. The error was thrown from line 2, but rethrown in line 4. The stacktrace should show the source of the original throw (line 2), not the source of the rethrow (line 4). You'll find this as bug <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=363543">363543</a> in Rhino's bugzilla. (I'd suggest that you vote for it, but you can't vote for bugs in Rhino!)</p>
<p>But the second problem is actually way more serious: it's missing the error message of the exception ("urk")! That's bug <a href="%3Ca%20href=" id="351664"">351664</a> (recently patched, but not yet released in any official Rhino release). Since the JS interpreter didn't get the line numbers right either, you now know nothing about the cause of the exception!</p>
<p>Unfortunately, bug 351664 doesn't just apply to rethrown exceptions... it applies to regular old thrown exceptions, too. Here, eval this:</p>
<p><code> throw new Error("oh no!");</code></p>
<p>All you get is "<code>[object Error]</code>"... no hint of the error message.</p>
<h3>Java 6 won't compile JS into .class files</h3>
<p>The coolest feature of Rhino, IMO, was its tight integration with Java classes; you could compile JS directly to .class files that you could run directly in the JVM. (The IKVM guys used this trick to run <a href="http://zumbrunn.com/mochazone/Rhino,+Mono,+IKVM.+Or:+JavaScript+the+hard+way/">JS on Mono</a>.)</p>
<p>While Java 6 provides new support for a Compiler API, it doesn't even provide built-in support for compiling a string (you have to roll your own classes for that little convenience), and absolutely no integration with any other compiler, including the Rhino compiler. That means no JS files as classes.
</p>
<h3>Java 6 has no JavaScript debugger</h3>
<p>But aside from inheriting all of the problems in the Rhino implementation, you'll soon find that Java 6 is lacking a critical feature present in Rhino. Rhino includes a JavaScript debugger, allowing you to set breakpoints, step in, step over and step return throughout your code, as well as set up watch expressions, observe the state of local variables, and so on.</p>
<p>But the new JSR 223 scripting API includes no debugger support. There's no way to stop on line 2330 of a long JavaScript file, set breakpoints, observe script-level variables, etc. The Java 6 scripting engine is just a <a href="http://java.sun.com/javase/6/docs/api/javax/script/ScriptEngine.html">black box with an eval function</a>.</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com1tag:blogger.com,1999:blog-19651018.post-1166775010751475772006-12-22T00:09:00.000-08:002006-12-22T00:10:10.786-08:00Implementing a JavaScript Language Interpreter for Selenium Testing<p>In <a href="http://www.blogger.com/comment.g?blogID=19651018&postID=116677250027939474">another article</a>, I explained why HTML Selenese has no support for "if" statements and conditional "for" loops. There, I argued that implementing flow control "properly" in <a href="http://www.openqa.org/selenium-core/">Selenium Core</a> would require writing an entire language parser/interpreter in JavaScript. Here I'll catalog some of our failed attempts to do precisely that.</p>
<h3>Use the Browser's Native JS Parser</h3>
<p>The first question everybody asks us is: "why don't you let people write Selenium tests in JavaScript directly, rather than writing them in HTML Selenese tables?" JavaScript already includes (as part of its language) an "eval" function that can execute arbitrary JavaScript encoded as a string; JavaScript will parse the string, interpret the code, and even return the final result.</p>
<p>You can actually write tests in JavaScript today using <a href="http://www.openqa.org/selenium-rc/">Selenium Remote Control</a> and <a href="http://www.mozilla.org/rhino/">Rhino</a>, the JavaScript interpreter for Java. The disadvantage of this is that your JS test runs in its own separate process (a Java JVM), and it requires you to set up a Selenium Server. It doesn't run the JavaScript directly in the browser.</p>
<p>Using Selenium RC, you can write a JS test like this: </p>
<p><code> selenium.open("/mypage");<br>
selenium.type("login", "alice");<br>
selenium.type("password", "aaaaa");<br>
selenium.click("login");<br>
selenium.waitForPageToLoad(5000);<br>
selenium.click("link=Colors");<br>
selenium.waitForPageToLoad(5000);<br>
selenium.click("blue");<br>
selenium.waitForPageToLoad(5000);<br>
if (!selenium.isTextPresent("blue moon")) throw new Error("blue not present!");<br></code></p>
<p>However, this test requires Selenium RC and a running Selenium Server. You can't run that test directly in the browser for a very important reason: JavaScript has no "sleep" function; the JavaScript interpreter in all browsers is, by design, single-threaded. That means that there's no way to implement "waitForPageToLoad" as it's written here. In another language you might implement a function like "waitForPageToLoad" by doing something like this:</p>
<p><code> function waitForPageToLoad() {<br>
while (!isPageLoaded() && !timeIsUp()) {<br>
sleep(500);<br>
}<br>
}</code></p>
<p>How can we do this without a sleep function? Let's try a standard "busy wait" function (i.e. constantly perform mathematical calculations until our time is up).</p>
<p><code> function sleep(interval) {<br>
var i = 0;<br>
for (var start = now(); (start + interval) > now();) {<br>
i++;<br>
}<br>
}<br>
<br>
function runTest() {<br>
frameLoaded = false;<br>
var myFrame = document.getElementById('myFrame');<br>
var myWindow = myFrame.contentWindow;<br>
myWindow.location = "hello.html";<br>
var start = now();<br>
var finish = start + 5000;<br>
while (!frameLoaded && finish > now()) {<br>
sleep(500);<br>
}<br>
alert("frameLoaded="+frameLoaded);<br>
}</code></p>
<p>You can try this test here: <a href="http://www.theblackforge.net/jswait/busywait.html">Busy Wait</a>. I tested it in Firefox and IE. What you'll find is that the window frame refuses to load as long as the busy wait loop is running. As soon as the test is finished (failing), the frame loads. (In FF the frame loads while the alert pop-up appears; in IE the frame doesn't load until after you click OK.)</p>
<p>Under the hood, here's what's really happening: when you set the window.location in JavaScript, you haven't actually done anything yet; you've just scheduled a task to be done. Once your task is completely finished, the browser goes on to perform the next task on the queue (which, in this case, is loading up a new web page).</p>
<p>To put that another way, there's a <i>reason</i> why JavaScript has no "sleep" function: architecturally, it would be pointless. Normally, you sleep for a few seconds while something else happens in the background. But as long as your JavaScript is running in the foreground, <i>nothing</i> can happen in the background! (Try clicking around in the menus, or even clicking the close button, while the busy wait test is running. The entire browser is locked!)</p>
<p>For a while, I thought I had found a clever workaround to this problem: using a Java applet to do the sleeping. You can see this in action here: <a href="http://www.theblackforge.net/jswait/appletwait.html">Applet Wait</a>. The page loads! That's great. But then we try to wait for the additional salutations onLoad... and they never come. (If you run "top" or the Windows Task Manager you can see that this mechanism doesn't pin the CPU; the CPU is idle.)</p>
<p>Actions you perform in JavaScript can have multi-threaded effects, even adding items to the queue of work to be done, but as long as any JS function is working, no other <i>JavaScript</i> work can happen in the background.</p>
<h3>Waiting With <code>setTimeout</code></h3>
<p>So how does Selenium do it? JavaScript exposes a simple mechanism that allows you to schedule events for the future: setTimeout. It places an item on the queue of events to be done, after a short delay. It's like saying to the JS interpreter "run this snippet of code (as soon as you can) 500 milliseconds from now." That parenthetical "as soon as you can" is critical here, because of course, if the JS interpreter is busy at that time (e.g. if somebody is working or is in the middle of a busy wait,) the timeout event won't happen until that earlier job is done.</p>
<p>That means that you still can't write the JS test I highlighted at the beginning of this article:</p>
<p><code> selenium.click("blue");<br>
selenium.waitForPageToLoad(5000);<br>
if (!selenium.isTextPresent("blue moon")) throw new Error("blue not present!");</code></p>
<p>Because you cannot simply sleep <i>and then assert something</i>. Instead, you have to let the interpreter sleep on your behalf, and then call you back when it thinks you might be done.</p>
<p>The <a href="http://script.aculo.us/">Scriptaculous</a> <a href="http://wiki.script.aculo.us/scriptaculous/show/Test.Unit.Runner">Test.Unit.Runner</a> exposes a common mechanism for writing asynchronous tests using setTimeout: a "wait" function, appearing as the very last line of your test, that includes the "rest" of your test (everything to be done after you're finished waiting). It must be the very last line of your test because (like the setTimeout function), "wait" simply schedules an event to happen in the future; it doesn't synchronously wait for the job to get done. A Test.Unit.Runner test looks a little like this:</p>
<p><code> selenium.click("blue");<br>
waitForPageToLoad(5000, function() {<br>
if (!selenium.isTextPresent("blue moon")) throw new Error("blue not present!");<br>
}</code></p>
<p>That might not look so bad from here... but remember that you have to nest these "wait" statements every time you want to wait; a standard Selenium test needs to wait after almost every line! So the simple looking JS test I quoted at the beginning of this article would look like this:</p>
<p><code> selenium.open("/mypage");<br>
selenium.type("login", "alice");<br>
selenium.type("password", "aaaaa");<br>
selenium.click("login");<br>
waitForPageToLoad(5000, function() {<br>
selenium.click("link=Colors");<br>
waitForPageToLoad(5000, function() {<br>
selenium.click("blue");<br>
waitForPageToLoad(5000, function() {<br>
if (!selenium.isTextPresent("blue moon")) throw new Error("blue not present!");<br>
}<br>
}<br>
}</code></p>
<p>Every time you "wait", you have to create a new nested block. This code starts to look pretty gnarly pretty quickly... But that's really the least of it. The real kicker is that you can't really use for loops together with "wait" statements!</p>
<p>Don't forget, the "wait" statement has to appear at the <i>end</i> of your function. That means that you can't click/wait/assert on 3 things in a for loop, like this:</p>
<p><code> for (var i = 1; i <= 3; i++) {<br>
open(page[i]);<br>
wait(50, {<br>
assertTrue(document.foo == i);<br>
}<br>
}</code></p>
<p>Naively, we might assume that this code would do the following (which is what we'd want):</p>
<p><code>open(page[1])<br>
sleep(50)<br>
assert(foo==1)<br>
open(page[2])<br>
sleep(50)<br>
assert(foo==2)<br>
open(page[3])<br>
sleep(50)<br>
assert(foo==3)</code></p>
<p>But, instead, that code will try to open pages 1, 2 and 3 without any delay between them,[*] then wait 50ms, then do all three of your assertions. In other words, you'd get this:</p>
<p><code>open(page[1])<br>
open(page[2])<br>
open(page[3])<br>
sleep(50)<br>
assert(foo==1)<br>
assert(foo==2)<br>
assert(foo==3)</code></p>
<p>[*] In fact, it's even worse than that, because page 1 wouldn't really open until your JS function was finished running. So really it would only ever open page3 and then do all three assertions on page 3.</p>
<p>I don't want anyone to get the mistaken impression that I think the Scriptaculous Test.Unit.Runner "wait" function is altogether bad. It's a useful function for doing unit testing, where you'll test one or maybe two asynchronous events at a time. (Good unit tests test only one thing at a time, after all.) It's a clever solution to a difficult problem.</p>
<p>But in functional integration tests like ours, where you have to wait after almost every line, using the "wait" function doesn't really help that much; it doesn't give you the ability to write complicated integration tests in a fully-powered language with logic, try/catch, and recursion.</p>
<p>"wait" gives you <i>some</i> of the power you need/expect in a modern programming language, but not enough: although you can use if statements and for loops <i>within</i> each nested "wait" block, you can't use them <i>across</i> code blocks. (You still can't put an "open" command in a while loop, or in a try/catch block.)</p>
<h3>Using Javascript in HTML Selenese</h3>
<p>In fact, HTML Selenese makes it pretty easy for you to write asynchronous tests in JavaScript, simply by scheduling a timeout between every line of your Selenese test. Since Selenese also supports running arbitrary JS in a table cell, it's not too hard to write your test like this:</p>
<table border="1">
<tbody><tr><td>storeEval</td><td>selenium.open("/mypage");</td></tr>
<tr><td>storeEval</td><td>selenium.type("login", "alice");</td></tr>
<tr><td>storeEval</td><td>selenium.type("password", "aaaaa");</td></tr>
<tr><td>storeEval</td><td>selenium.click("login");</td></tr>
<tr><td>storeEval</td><td>selenium.waitForPageToLoad(5000);</td></tr>
<tr><td>storeEval</td><td>selenium.click("link=Colors");</td></tr>
<tr><td>storeEval</td><td>selenium.waitForPageToLoad(5000);</td></tr>
<tr><td>storeEval</td><td>selenium.click("blue");</td></tr>
<tr><td>storeEval</td><td>selenium.waitForPageToLoad(5000);</td></tr>
<tr><td>storeEval</td><td>if (!selenium.isTextPresent("blue moon")) throw new Error("blue not present!");</td></tr>
</tbody></table>
<p>That's not quite as nice as the test you can write with Selenium RC and Rhino, though, for a couple of reasons. First, it suffers from all of the same problems as the Scritaculous "wait" function: although you can use "if" statements and "for" loops <i>within</i> each storeEval block, you can't use them <i>across</i> functional blocks. (Again, you still can't put an "open" command in a "while" loop, or in a "try/catch" block.)</p>
<p>But in fact it's even a little worse than Test.Unit.Runner, because it means that you can't write a test like this:</p>
<table border="1">
<tbody><tr><td>storeEval</td><td>var foo = 'bar';</td></tr>
<tr><td>storeEval</td><td>selenium.click(foo);</td></tr>
</tbody></table>
<p>That's because "foo" was defined as a local variable in the first block; it goes out of scope as soon as the block finishes, and is undefined by the time we start the second block. (Test.Unit.Runner doesn't suffer from this problem, because it uses closures to encapsulate the variables from the first block inside the second block.)</p>
<h3>Generating Turing-Complete Selenese</h3>
<p>Some people, when they see the storeEval tables I show above, start to thinking: "perhaps I could <i>generate</i> that table, from code written in a high-level functional language."</p>
<p>And, of course, you certainly could. But, as we know, you certainly can't convert JS like this into Selenese:</p>
<p><code> for (var i = 0; i < 3; i++) {<br>
selenium.open("page" + i);<br>
}</code></p>
<p>... or could you? If Selenium had an "gotoIf" command, you could write the code like this:</p>
<table border="1">
<tbody><tr><td>store</td><td>i</td><td>0</td></tr>
<tr><td>gotoIf</td><td>!(i < 3)</td><td>5</td></tr>
<tr><td>storeEval</td><td>selenium.open("page"+i)</td><td></td></tr>
<tr><td>storeEval</td><td>i</td><td>i+1</td></tr>
<tr><td>gotoIf</td><td>TRUE</td><td>1</td></tr>
<tr><td>echo</td><td>done</td><td></td></tr>
</tbody></table>
<p>Every "for" loop is just a convenient way of saying "gotoIf"; with the addition of a simple command to the language, we would make it possible to translate simple "if" and "for" statements into Selenese. "gotoIf" makes Selenese "Turing-complete". (In fact, there's a Selenium Core extension that provides this; I discuss the flowControl extension in more detail in an <a href="http://www.blogger.com/comment.g?blogID=19651018&postID=116677250027939474">earlier article</a>.)</p>
<p>"if" and "for" statements are easy, but what about try/catch statements? What about JavaScript functions? What about strings run in "eval" blocks? How would we handle scoped variables (and guarantee that they go out of scope correctly)? </p>
<p>If you understand this problem completely, you quickly see that the "translator" of JavaScript into Turing-complete Selenese is really a full <i>compiler</i>; it would require a complete language parser and interpreter to know how to translate try blocks, functions, nested scopes, etc. into their "gotoIf" equivalents. It wouldn't be inappropriate to call it "Selenese Assembly", since it has a lot in common with assembly language: it's powerful enough to handle anything, but so complicated to write that you probably wouldn't want to write a lot of it by hand.</p>
<p>As I argued in the previous article, writing a full compiler for "Selenese Assembly" would be a lot of work, for basically no benefit, because we already have JavaScript support in Selenium RC.</p>
<h3>Writing a Full Language Parser in Javascript</h3>
<p>Writing a compiler for "Selenese Assembly" <i>would</i> be a lot of work, but that didn't stop a few people from trying.</p>
<p>Somebody <i>has</i>, in fact, written a language interpreter in JS: Brendan Eich, the "father" of JavaScript, has written a meta-circular JavaScript interpreter in JavaScript called "Narcissus" (named after the Greek myth of the boy who fell in love with his own reflection). Jason Huggins, the original author of Selenium, began working on trying to integrate Narcissus with Selenium, but never finished.</p>
<p>The reason why he never finished is because we don't <i>merely</i> need a JS interpreter written in JS. To implement a "sleep" function, we would <i>also</i> need the meta-circular interpreter to be able to interrupt its flow of execution using setTimeout. That means that the meta-circular interpreter would need to be written under all of the constraints that I described in the previous section: it can't use any local variables (except as temporary storage until they get written out to permanent global variables) and it has (at best) limited use of for loops, try/catch blocks, and other language features of JavaScript.</p>
<p>Another way of putting that is that the meta-circular interpreter would need to be written in "continuations-passing style", meaning that all of the information about the current state of the running JS program (what line you're on, where you are in the stack, all of the local variables, scopes, etc.) would need to be stored in a variable that would be "passed around" to all of the other functions; this would allow you to "setTimeout" in the future, simply passing in the continuation object to the next chunk of your code.</p>
<p>Take a look at the Narcissus code for yourself... it's pretty complicated. Rewriting it in continuations-passing style would require rewriting the whole thing from scratch... with one hand tied behind your back!</p>
<p>Having said that, there is another language in the world that is considerably easier to parse and interpret, and which actually makes it very easy to write your code in that style: LISP. And, indeed, someone has written a <a href="http://www.joeganley.com/code/jslisp.html">LISP interpreter in JS</a>. Rewriting that code to support calls to "setTimeout" probably wouldn't be too hard, and at that point, you'd be able to write in-browser Selenium tests using the full power of LISP.</p>
<p>The only disadvantage? You'd have to write your tests in LISP! ;-)</p>
<p>Oh, and did I mention that Bill Atkins has written a <a href="http://galoot.org/%7Ebill/code/selenium-lisp.tar.gz">Selenium LISP client</a>? Or that you can use the <a href="http://sisc.sourceforge.net/">SISC</a> Java-based Scheme interpreter with our existing Java Client Driver?<br>
</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com0tag:blogger.com,1999:blog-19651018.post-1166772500279394742006-12-21T23:27:00.000-08:002006-12-21T23:28:20.293-08:00Why Is HTML Selenese So Simplistic?<p>Recently, some people have asked on the <a href="http://forums.openqa.org/forum.jspa?forumID=3">Selenium mailing list</a> why HTML Selenese (the language of Selenium Core) doesn't include conditional "if" statements or "for" loops, or, more generally, why there isn't some way to reuse code (with functions or subroutines). Adding them in would be pretty straightforward. (Andrey Yegorov has written a "<a href="http://wiki.openqa.org/display/SEL/flowControl">flow control</a>" user extension that provides support for "if/goto" statements in HTML Selenese, but you really shouldn't use it.) In this article, I'm going to explain why we don't just add support for conditionals and loops directly into Selenium Core (which is also why, in my opinion, humans shouldn't write tests that use the flow control extension). In some cases, nothing is better than something, when the something is considered harmful.</p>
<p>First, I want to highlight that it is absolutely possible to write Selenium tests with "if" statements and "for" loops, just not in HTML Selenese. <a href="http://www.openqa.org/selenium-rc/">Selenium Remote Control</a> (S-RC) provides support for writing Selenium tests in any language, including Java, .NET, JavaScript, Perl, PHP, Python, and Ruby. Officially, if you need to write a test that has an "if" statement in it, we recommend that you write your test in S-RC.</p>
<h3>Goto Considered Harmful</h3>
<p>The main reason why we don't want to add support for a "goto" statement in HTML Selenese is that the <a href="http://www.acm.org/classics/oct95/">Go To Statement Is Considered Harmful</a>. In his classic 1968 article for the Association for Computing Machinery, Edsger W. Dijkstra successfully argued that use of "go to" statements (or "jump" instructions) makes one's code less intelligible, and that "the go to statement should be abolished from all 'higher level' programming languages (i.e. everything except, perhaps, plain machine code)".</p>
<p>The "goto" statement undermines the intelligibility of your code by eliminating the possibility of a clean representational structure. Code based on "goto" statements quickly turns into "spaghetti", as the code jumps all over your program, seemingly without reason. (If you've ever had to maintain a lot of code written in Basic, you know what I mean.) That's why people invented higher level languages like Java, C#, JavaScript, Perl, PHP, Python, and Ruby.</p>
<p>Adding a "goto" statement would completely undermine the entire point of HTML Selenese: simplicity. Straightforward non-branching tables are easy for non-programmers to read and understand. Many Selenium users report that their customers are able to grok Selenium tests (and even fix bugs in them), which gets more people involved in the testing/requirements gathering process. If we added "goto" to Selenese, and people actually used it, that kind of end-user/developer interaction would come grinding to a halt.</p>
<h3>Nobody Needs to Write a Goto Statement</h3>
<p>Ultimately, though, all of our programs are ultimately compiled down and assembled into plain machine code, which makes ample use of "jump" instructions in order to get its job done. Under the hood, all of the fancy object-oriented features available in higher level languages are automatically translated into "goto" statements by compilers (who guarantee the correctness of the resulting assembly code).</p>
<p>[In fact, as I understand it, that's exactly how Yegorov uses his flow control extension: he never (or rarely) writes HTML Selenese by hand, but rather he <i>generates</i> his HTML Selenese from another higher-level programming language that makes no direct reference to "goto" statements. In a very real sense, he "compiles" a higher-level language into HTML Selenese. (Please correct me if I'm wrong about this; I'm basing this on an <a href="http://forums.openqa.org/thread.jspa?threadID=996">old post of his</a> from March 2006.)]</p>
<p>That's certainly one way to do it, but I argue that it's totally unnecessary. He could simply <i>run</i> his tests in a high-level language directly with Selenium RC.</p>
<h3>Implementing Proper Flow Control</h3>
<p>Could we implement "proper" flow control in HTML Selenese, instead of simple "goto" statements? Think about what that means: if, for, while, do/until, try/catch, scoped variables, iterators, closures... these are programming language features. To implement proper flow control in Selenium Core, we'd have to implement (and maintain) an entire language parser and interpreter <i>written in JavaScript</i>.</p>
<p>For one thing, that's too difficult. While JS interpreters have been getting more and more compatible as time goes on, it's still very difficult to write a complicated program that behaves the same way in all browsers, say nothing of writing a full language parser/interpreter in JS.</p>
<p>For another, this problem has already been solved for us! Java, .NET, JavaScript, Perl, PHP, Python and Ruby all have excellent parsers, compilers and interpreters. Why would we reinvent that wheel in JavaScript?</p>
<p>(In another article, I'll write up exactly how far people have gone in this direction... it's not pretty.)</p>
<h3>Easier to Read/Translate Without Flow Control</h3>
<p>Finally, there are two important advantages to not supporting flow control in HTML Selenese, even if we could do it "right".</p>
<p>1) By limiting the expressive power of the language, we make Selenese substantially easier to translate into other languages, as we do in Selenium IDE. You can use Selenium IDE to translate HTML Selenese directly into any language that we support in Selenium RC. If we supported writing tests in a "full language", we wouldn't be able to provide translators into all of those other languages. (Language translation is hard; the problem is substantially simplified when you don't have any sophisticated language constructs to translate.)</p>
<p>2) HTML Selenese is about simplicity; that's the whole reason the language exists. Turning HTML Selenese into a full-blown scripting language, with all the advantages that would bring, would still undermine its simplicity (though not as badly as implementing "goto").</p>
<p>If you want a scripting language, <i>use a scripting language</i>! We support that 100%! :-)</p>
<hr>
<p>In conclusion: implementing flow control mechanisms in Selenium Core means supporting "goto" statements (which sucks) or supporting a full language parser/interpreter (which is brutally hard). Since Selenium RC already supports the native flow control mechanisms available in Java, C#, JavaScript, Perl, PHP, Python and Ruby, and since the whole point of HTML Selenese is its simplicity, there's almost no payoff in implementing yet another parser/interpreter in JavaScript.</p>
<p>In another article I'll discuss a few of our failed attempts to support a full language parser/interpreter in JS.</p>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com6tag:blogger.com,1999:blog-19651018.post-1166770837402807722006-12-21T22:47:00.000-08:002006-12-21T23:01:42.533-08:00Compass as compared with Maven's SNAPSHOT system<p>In this article, I'll describe some of the differences between Maven 2.x and the "Compass" interal home-grown system we use at work. I'll first describe our repository layout, then describe our component descriptor file, and finally I'll summarize some of the advantages and disadvantages of using the different systems and suggest future work.</p>
<p>The Compass system was designed with Maven 1.x in mind. The original developers had said, roughly: "You know, Maven's got the right idea, but this really hasn't been implemented the way we'd want it. We should rewrite it ourselves from scratch."</p>
<h3>Repository Layout</h3>
<p>Like Maven, Compass has one or more remote repositories containing official built artifacts, (or "components", as we call them,) as well as a local repo on each developer's machine which caches artifacts from the remote repo and contains locally built artifacts. Where Maven and Compass substantially diverge is in how artifacts are stored in the repository.</p>
<p>While Compass doesn't have a notion of "groupId", our remote repository is divided up into sections, like this:</p>
<p> thirdparty/<br> log4j/<br> junit/<br> firstparty/<br> RECENT/<br> foo/<br> bar/<br> INTERNAL/<br> foo/<br> bar/<br> RELEASE/<br> foo/<br> bar/</p>
<p>[NOTE: This isn't <i>exactly</i> how it looks, but it's close enough.]</p>
<p>Within a given section, you find a flat list of components. In this example, "foo" and "bar" are buildable components that we've created; log4j and junit are, naturally, components built by other people. "RECENT" contains only freshly built components. "INTERNAL" contains components that have been blessed by some human being, and are intended for internal consumption. "RELEASE" contains released components and products.</p>
<p>In practice, there are 914 components in RECENT and 671 components in INTERNAL.</p>
<p>Within a given component directory, you'll find a number of subdirectories, which define the "version" of the component. Thirdparty versions may have any arbitrary strings for their names (e.g. "3.8.1" "1.0beta3" "deerpark"). However, firstparty versions are strictly defined: they are simply the P4 Changelist number of the product at the time it was built.</p>
<p>(A quick note about changelist numbers as opposed to revision numbers. Most people are familiar with the distinction between CVS revision numbers and SVN revision numbers: CVS revision numbers are "per file" whereas SVN revision numbers are global to the repository. P4 changelist numbers are like SVN revision numbers. [Also note that you can calculate something like an SVN revision number in CVS, simply by noting the timestamp of the most recent check-in.])</p>
<p>So, within the "foo" directory in RECENT, you'll see this:</p>
<p> foo/<br> 123456/<br> foo.jar<br> 123457/<br> foo.jar<br> 123458/<br> foo.jar<br> @LATEST -> 123458</p>
<p>That's three numbered directories with a "LATEST" symlink, pointing to the most recent build in that directory.</p>
<p>The first thing to note about this system is that if you build 123458 and then rebuild 123458, it will replace the old "123458" directory. The second thing to note is that if you change foo at all, it will get a new changelist/revision number, and so it will get a new subdirectory under "foo" once automatically built.</p>
<p>The three sections within the "firstparty" directory (RECENT, INTERNAL, RELEASE) are called "release levels", and we have a process about how components move into each release level. "foo" and "bar" are automatically built every night and deployed into RECENT; if there are more than three builds in RECENT, we automatically delete the oldest build.</p>
<p>If somebody thinks that a build of "foo" is good enough to keep around, they "promote" that build into INTERNAL by simply copying the numbered changelist directory into INTERNAL. Once we think it's good enough to release, we can promote that INTERNAL build into RELEASE by copying it there. There is no tool, nor any need for a tool, to rebuild for release or make even the slightest changes to the released binaries.</p>
<p>Especially note that we don't put any of this information in the filename of the jar. It's called "foo.jar" whether it's in RECENT, INTERNAL, or RELEASE. We <i>do</i> burn the changelist number of foo.jar into its META-INF/manifest.mf at build-time... that information remains constant whether "foo.jar" is copied to INTERNAL or RELEASE.</p>
<h3>Component Descriptor File</h3>
<p>Compass has a file that looks a lot like the Maven POM XML file... our file is called "component.xml". component.xml defines a list of <dependency> elements. Here's an example component.xml file:</p>
<p><component><br> <name>foo</name><br> <release>6.1.0</release><br> <branch>main</branch><br> <depends><br> <dependency type='internal'><br> <name>bar</name><br> <branch>2.1.x</branch><br> <version>242483</version><br> <release-level>RECENT</release-level><br> </dependency><br> <!-- ... --><br> </depends><br></component></p>
<p>Note that the component does not declare its own version number. (Since in Compass-lingo, version numbers are SCM revision numbers, it would be impossible to declare this in the descriptor file; as soon as you checked in, it would be wrong!) Instead, it allows you to declare a "branch" name, usually something like "main" or "feature" or "2.1.x" as you see above, as well as a "release", which looks more like a Maven version number, but is purely descriptive... it's not used for resolving artifacts at all.</p>
<p>Also note the presence of the <release-level> tag in the <dependency> element, which specifies the release level (RECENT, INTERNAL, RELEASE) of the dependency in question.</p>
<p>We do have a simple tool that automatically verifies whether a component is suitable for promotion to INTERNAL or RELEASE, which we call "DepWalker" (Dependency Walker). You can use it to check to see if "foo" depends on any components in RECENT, or whether anything "foo" depends on (or anything <i>they</i> depend on, etc.) depends on components in RECENT. Components in RECENT are temporary, and therefore unsuitable for long-term reproducibility.</p>
<p>Of course, if you like, you can also wire up your <dependency> tag to depend on RECENT/bar/LATEST. In that case, you can continuously integrate with the latest version of bar. You do that like this:</p>
<p><dependency type='internal'><br> <name>bar</name><br> <branch>main</branch><br> <version label='LATEST'>242483</version><br> <release-level>RECENT</release-level><br></dependency></p>
<p>The presence of the attribute "label='LATEST'" informs Compass that we want to automatically upgrade to the current LATEST version of "bar" that's available.</p>
<p>In an optional step we call "pre-build", Compass automatically modifies the number in <version> to match the LATEST version number. If you don't "pre-build", the existing version number will be used. The official nightly build system always runs "pre-build", and then automatically checks in the updated version into source control.</p>
<p>With that said, you don't have to use label=LATEST if you don't want to. If you don't care about reproducibility, you can just say this:</p>
<p><dependency type='internal'><br> <name>bar</name><br> <branch>2.1.x</branch><br> <version cl='242483'>LATEST</version><br> <release-level>RECENT</release-level><br></dependency></p>
<p>Since the version number is LATEST, we can't reproduce this build later. In that case, the automated build system still performs automated check-ins to modify the "cl='242483'" attribute, but that information is only there so humans can know what "LATEST" was at a given time, and so we can automatically bump the version number (by checking in, we increase the revision number). </p>
<p>Finally, if you <i>really</i> don't care about reprodicibility, you say this:</p>
<p><dependency type='internal'><br> <name>bar</name><br> <branch>2.1.x</branch><br> <version>LATEST</version><br> <release-level>RECENT</release-level><br></dependency></p>
<p>In that case, your build is totally unreproducible, but it has the advantage that we won't bother with automated check-ins.</p>
<h3>Advantages/Disadvantages Relative to the Current "Snapshot" System</h3>
<p>Advantages:</p><ul><li>"SNAPSHOT" is a marker that indicates that a given component/project is under development. But that means that you necessarily have to modify the binaries in order to release them; using today's release plug-in, you actually have to check in modified source code before you can release, which is <i>really</i> troubling.
<p>The Compass system doesn't use a "SNAPSHOT" marker, and so promoting/releasing is simply a copy step.</p></li>
<li>Under today's SNAPSHOT system, there's no notion of a "build number" for a non-SNAPSHOT release. If you deploy foo-1.0 today, then make some changes and redeploy foo-1.0, today's deploy system will simply replace the old foo-1.0 with the newer version.
<p>In Compass, everything always has a build number (and it's the same as the SCM revision number).</p></li>
<li>label=LATEST guarantees reproducibility while allowing for "soft" version numbering. Maven only allows for unreproducible "soft" version numbers.
<p>Reproducibility is generally a virtue, but specifically it pays off when "foo" depends on "bar" and "bar" introduces a change that breaks "foo". When "foo" is a reproducible build, you can say "this (automated) check-in 123456 broke the build of foo", see what changed in that check-in, and immediately identify the source of the problem (a bad new "bar"). It also allows you to roll back to an older check-in of "foo" to fix the problem.</p>
<p>With unreproducible "soft" version numbers, you find yourself automatically upgraded to the new "bar", and no reliable way to determine this. The same code in "foo" may build successfully on Tuesday but fail on Wednesday, with no apparent explanation as to why.</p></li>
</ul>
<p>Disadvantages:</p><ul><li>If you don't use a SNAPSHOT marker, it's not as easy to tell whether the file in question is an official release or not. (This may matter a lot more to open source developers than closed source developers. Open source developers make pre-release builds available for public download, which creates a risk that someone may download a pre-release binary and then ask for support. Closed source developers typically keep pre-release binaries a secret and only make release binaries available through official channels.)
<p>Since typically under the Compass system even the filename doesn't include version information, the only way to figure out the "version" (changelist/revision#) of a given jar is to crack it open and look at the manifest.mf file. Even that won't tell you whether the jar has been officially released, but it should be enough information for you to check to see if it is an official release at all.</p></li>
<li>label=LATEST does automated check-ins... in some cases, it does a <i>lot</i> of automated check-ins. These can clutter your revision logs.</li>
</ul>
<h3>Conclusion</h3>
<p>I wouldn't want to suggest from this writing that anyone should cast aside the existing "SNAPSHOT" system in favor of Compass. However, it is a major goal of mine to make Maven's release mechanism powerful enough that we <i>could</i> follow/enforce the Compass system using Maven.</p>
<p>Here's what I'd like to do, in no particular order:</p>
<ul>
<li>Allow users to optionally specify a named repo within which you require that a given artifact can be found. (Internally, we'd probably use this as an equivalent to our <release-level> tag, but I think this would be generally useful just to make it simpler to diagnose problems when a given artifact can't be found.)</li>
<li>Modify Maven's deploy mechanism and repository layout to ensure that a build number is always available in the remote repository, even for official releases.</li>
<li>Allow POMs to declare a dependency on a particular jar/version/build number, even for official releases.</li>
<li>Enhance the build-numbering mechanism to allow Continuum and other continuous build engines to deploy using an SCM revision number as a build number.</li>
<li>Create a mechanism that will allow you, if you wish, to automatically upgrade dependencies in POM files, by declaring both a literal version number + build number as well as a "soft" version number which serves only as a guideline for the automatic upgrade tool.</li>
</ul>Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com1tag:blogger.com,1999:blog-19651018.post-1138592193060236072006-01-29T19:36:00.000-08:002006-01-29T19:44:36.230-08:00Palm OS Development Suite (PODS) in Anger<p>First post, I guess. I've decided to create a separate blog for my random technical musings.</p>
<p>Today, I wrassled with the <a href="http://www.palmos.com/dev/tools/dev_suite.html">Palm OS Developers Suite (PODS)</a>. Getting it installed was a real challenge, so here's the short version of what I learned today:</p>
<p>If you already have <a href="http://www.cygwin.com/">Cygwin</a> installed:</p>
<ol start="0">
<li>Make sure you already have all of Cygwin's relevant "devel" packages.</li>
<li><a href="http://www.palmos.com/dev/tools/dev_suite.html">Download PODS from PalmSource</a>. (Create a free login.)</li>
<li>Run the PODS installer, but don't let it install its own Cygwin.</li>
<li>Download the Cygwin bz2 files from the <a href="http://sourceforge.net/project/showfiles.php?group_id=4429">PRC-Tools download page</a></li>
<li>Create a <a href="#dummysetup">dummy setup.ini</a> file and put it in the same directory as the bz2 files</li>
<li>Point Cygwin at your local directory to make it install the bz2s.</li>
<li>Make sure Cygwin and the other Palm resources are added to your path</li>
<li>Run "palmdev-prep" to inform it where you installed Garnet</li>
<li>Launch Eclipse, create a 68k project with the sample application, and try to build it. It should build successfully.</li>
<li>Create a new Debug configuration. (You can't just "Run As" or "Debug As" the way you normally would debug in Eclipse.) "Run -> Debug..." Click on "Palm OS Application" in the "Configurations" panel, and click the "New" button at the bottom there. Click on "Target" and select the "Palm OS Garnet Simulator 5.4" (not the "Palm OS Emulator"; they're different). Click Debug.</li>
<li>You'll get a pop-up saying "The selected Target Environment is being launched. Press OK when it is done launching, or Cancel to stop the debug session." The Palm Simulator should load up, and ask you for a ROM file. For the Simulator (not the <i>Emulator</i>), the ROM files are in sdk-5r4\tools\Palm_OS_54_Simulator\debug. You can use the enUS or EFIGS (English/French/Italian/German/Spanish) ROM; I used "Simulator_Full_EFIGS_Debug.rom". The Simulator should start.</li>
<li>Back in Eclipse, click "OK". Your application should be paused on its first line of code in the "PilotMain" function, ready for you to begin debugging it. </li>
</ol>
<hr/>
<h3>Installing Pre-Requisite Software for PODS</h3>
<p>It used to be that compiling Palm software wasn't possible without shelling out big bucks for <a href="http://www.metrowerks.com/">Metrowerks CodeWarrior</a>. Times have changed, and it is now possible to compile Palm software using <a href="http://gcc.gnu.org/">GCC</a> and <a href="http://prc-tools.sourceforge.net/">PRC-Tools</a>. But configuring these tools on Windows is challenging to say the least. At a minimum, it requires <a href="http://www.cygwin.com/">Cygwin</a>, the Linux-like environment for Windows, whose setup tool is notoriously finicky. Moreover, PRC-Tools doesn't come with the "base" Cygwin packages, and PRC-Tools' automated Cygwin-ified installer is busted.</p>
<p>So the good folks at PalmSource decided that it would be a good idea to package up a known-good version of Cygwin + GCC + PRC-Tools, along with the <a href="http://www.eclipse.org/">Eclipse</a> C Development Toolkit (<a href="http://www.eclipse.org/cdt/">CDT</a>), and make an installer out of it.</p>
<p>If you like, you can go <a href="http://www.palmos.com/dev/tools/dev_suite.html">grab PODS for free from PalmSource</a>. You have to create a free login, but then you can download the installer and get to work.</p>
<p>All fine and dandy, but the version of Cygwin they packaged up was rather out of date. Worse, if you read the README instructions carefully, you'll find that you must <span style="font-style:italic;">not</span> install their Cygwin on top of your existing Cygwin, lest they undergo Total Protonic Reversal.</p>
<blockquote><font color="red"><span style="font-weight:bold;">NOTE</span></font>: If you choose to install the Palm OS Developer Suite provided Cygwin, you should remove all previous Cygwin installations since Cygwin does not support multiple installations of that product to coexist on one machine. Palm OS Developer Suite installer modifies the Cygwin registry settings as part of the installation process and therefore may negatively affect any previous installations.</blockquote>
<p>If you're like me and you already have Cygwin installed, you'll need to follow their readme and, using the Cygwin setup tool, carefully ensure that you have every package on the list that they demand. In my case, I already had all of them just by installing the basic "Devel" package for Cygwin, except for, of course, PRC-Tools.</p>
<p>I did mention that PRC-Tools' Cygwin installer is busted, didn't I? Well, it is. The <a href="http://prc-tools.sourceforge.net/install/cygwin.html">PRC-Tools Installation Instructions for Windows/Cygwin</a> suggest that you just point Cygwin at "http://prc-tools.sourceforge.net/install" and let 'er rip. Bad idea. Cygwin will actually use that path to look at this file: <a href="http://prc-tools.sourceforge.net/install/setup.ini">http://prc-tools.sourceforge.net/install/setup.ini</a>. That, in turn, is supposed to tell Cygwin where to download all of the packages referenced in the setup.ini file.</p>
<p>But, of course, it can't. Go on, try browsing to <a href="http://prc-tools.sourceforge.net/install/prc-tools/prc-tools-2.3-cygwin.tar.bz2">http://prc-tools.sourceforge.net/install/prc-tools/prc-tools-2.3-cygwin.tar.bz2</a>. It won't work, because that's not really a bz2 file. Instead, it's a redirect to a SourceForge.net HTML page, telling you about the site from which it will *really* do the download. Your browser can handle these circuitous redirects, but Cygwin's setup tool definitely can't.</p>
<p>Instead, I had to download the binaries myself from the <a href="http://sourceforge.net/project/showfiles.php?group_id=4429">PRC-Tools Download page</a>, and then hacking up my own setup.ini, that looks more like this:<blockquote><code><a name="dummysetup" id="dummysetup"></a># Cygwin-setup.exe config file for prc-tools et al.<br />
# DO NOT EDIT! This file was generated by gensetup.<br />
#<br />
setup-timestamp: 1090157492<br />
setup-version: 2.427<br />
<br />
@ prc-tools<br />
sdesc: "GCC for Palm OS and related tools"<br />
category: Base Devel<br />
requires: cygwin<br />
version: 2.3<br />
install: prc-tools-2.3-cygwin.tar.bz2 4285677 fb7bef9542d3ce6957535fd8315fcf0f<br />
<br />
@ prc-tools-arm<br />
sdesc: "GCC for Palm OS on ARM (armlets)"<br />
category: Devel<br />
requires: cygwin prc-tools<br />
version: 2.3<br />
install: prc-tools-arm-2.3-cygwin.tar.bz2 4459228 3bc19ac45a9aaee9f790b67f63f8cf97<br />
<br />
@ prc-tools-htmldocs<br />
sdesc: "Documentation for prc-tools, GCC, etc as HTML"<br />
category: Devel Doc<br />
version: 2.3<br />
install: prc-tools-htmldocs-2.3-cygwin.tar.bz2 854795 bfe8f60a69b5ccfa3d247567150d463a</code></blockquote>
I put that file, and all of the bz2 files, into one directory, and then used Cygwin to install everything from a "Local Directory". That finally got prc-tools installed, apparently successfully. (But wait, there's more.)</p>
<hr/>
<h3>Selecting the Correct Project Type: 68k, PNO, Protein</h3>
<p>Once I was done with that, I tried running the PODS installer to get PalmSource's old version of Eclipse (3.0, missing the cool CDT enhancements available in 3.1, sadly), and told it <i>not</i> to install their version of Cygwin. That seemed to work OK, until it finally came time to try building a sample Project.</p>
<p>I was attempting to follow the instructions from <a href="http://www.amazon.com/gp/product/076457373X/qid=1138578382/sr=8-2/ref=pd_bbs_2/104-3702190-4569562?n=507846&s=books&v=glance">Professional Palm OS Programming</a>, a book which seems like it has most of the information you want, even if it isn't laid out particularly well. (And, sadly, the almost total lack of information available on Google make this book an unfortunately good value.)</p>
<p>As you attempt to create a new Project in PODS, you are immediately given the choice between three types of Projects: "68k", "68k PNO (PACE Native Objects)", or "Protein". The difference here is that Palms used to run on Motorola 68k processors, but most of the modern ones now run on ARM. In order to ensure backwards compatibility, the ARM-based Palms emulate 68k processors, in what they call "Palm Application Compatibility Enviroment" or PACE . (68k used to be big-endian, ARM is little-endian, and PACE handles that sort of thing for you.) If you want to write a teensy bit of native code for the ARM, you can write PNOs, which are ARM-native objects that can be used by 68k applications. And if you want to go whole-hog and write your entire application ARM-native, that's called "Protein."</p>
<p>But there's a catch. (Isn't there always?) Even if you DID want to write your entire application in Protein (the latest and greatest), you wouldn't be able to run it on anything except a Palm device that runs Palm OS 6.0 "Cobalt". But, as of January 2006, <a href="http://www.google.com/search?q=palm%20cobalt%20devices">there are no Cobalt devices available for sale</a>, even though Cobalt was released in early 2004! PNO will still work for Palm OS 5.4 Garnet applications, if you insist on going ARM-native, but you'll still have to write your application in 68k and then link to your "real" code in PNO.</p>
<p>So, as of January 2006, it's still best to just write your application in 68k... forget about crazy native ARM stuff for now.</p>
<h3>Compling 68k applications: Configuring <code>palmdev-prep</code></h3>
<p>OK, so, back to PODS. If you try to create a Protein Project, (let's say you decide to create the Sample Application, which is a MineHunt game,) it will build and debug just fine. But if, realizing that no one can run anything written in Protein, you decide to create a 68k Project and then try to build <i>that</i>, you'll find that it won't compile. Instead, you'll get an error from prc-tools, suggesting that you try running "<a href="http://prc-tools.sourceforge.net/doc/prc-tools_6.html#SEC37">palmdev-prep</a>".</p>
<blockquote><p>Early versions of prc-tools contained a hard-coded list of directories to be searched. Unfortunately, more recently there has been a tendency for each new Palm OS SDK to introduce new directories unpredictably and sometimes even to rearrange the old ones. Hence the hard-coded list approach is no longer practical; instead, you can use <code>palmdev-prep</code> to generate a list of directories tailored to the SDKs and other Palm OS development material you actually have installed.</p>
<p>The <code>palmdev-prep</code> utility scans the standard PalmDev directory and any extra directories listed on its command line, and generates the options required to make GCC search as appropriate each of the subdirectories found under each of the root directories given. The "standard PalmDev location" is determined when prc-tools is configured; typically on Unix it is <tt>`/opt/palmdev'</tt> and on Cygwin it is typically <tt>`/PalmDev'</tt>, which typically corresponds to the Windows directory <tt>`C:\PalmDev'</tt>.</p></blockquote>
<p>Pay attention here, kids, because unless you had 20/20 foresight, you probably <i>didn't</i> install PODS into C:\PalmDev. Instead, you probably installed it into the default location, "C:\Program Files\PalmSource\Palm OS Developer Suite".</p>
<p>That means, to get your 68k project to compile, you'll need to make a symlink, in Cygwin. Launch your Cygwin bash shell and then do this:</p>
<blockquote><code>> cd /<br />
> ln -s "C:/Program Files/PalmSource/Palm OS Developer Suite" PalmDev<br />
> palmdev-prep</code> <code>"C:/Program Files/PalmSource/Palm OS Developer Suite"</code></blockquote>
<p>Even once I did this, I still got a warning saying: "<code>palmdev-prep: warning: can't open '/PalmDev/sdk-5r4/include/Core/CoreTraps.h': No such file or directory</code>", but it didn't appear to do any damage. At that point, my Palm 68k application compiled just fine.</p>
<h3>Debugging your Application</h3>
<p>Debugging Palm Applications is a bit awkward relative to debugging standard C applications or Java applications in Eclipse, because even though techinically you are creating what might be called an "application", the output of the compiler is a .prc file, not something that's actually executable on <em>your</em> operating system. The applications you actually use for debugging are the Simulators/Emulators that get installed with PODS.</p>
<p>Since you won't be using Cobalt until somebody finally gets around to creating a device for it, you'll probably mostly want to use the Garnet Simulator (Palm OS 5.4) or the Palm OS Emulator (POSE), which emulates devices up to Palm OS 4.1.2. To start up a Simulator/Emulator and use it, you'll need to select a ROM file... note that Simulator ROMs don't work with the Emulator, and vice versa. All of the ROMs you need should be included with PODS. POSE ROMs live in "PalmOSTools\Palm OS Emulator\ROMS"; Garnet ROMs live in "sdk-5r4\tools\Palm_OS_54_Simulator\debug". There are several Garnet ROMs, meant to support numerous languages. I tend to prefer the EFIGS ROM (English/French/Italian/German/Spanish), but you may prefer just the plain-vanilla enUS ROM. Regardless, always make sure you use the "Debug" ROMs instead of the "Release" ROMs, because they're the ones with the handy debugging facilities you'll use to debug your Palm application.
<p>Conveniently, Eclipse/PODS can be configured to automatically launch the Simulator of your choice, though it is a little bit screwy if you're familar with Eclipse. Instead of clicking on "Run As..." you need to click on Run -> "Debug..." and click on "Palm OS Application", then finally click on the "New" button to create a new configuration. In the "Target" tab, you're allowed to pick your own Simulator/Emulator. (If you're clever, you can even use this to debug a real live running Palm plugged into your computer over a serial port (COM1/COM2) or over USB, but I haven't tried it yet.) I recommend the Garnet Simulator for now.
<p>At that point, you should be able to click the "Debug" button on your configuration to launch the Garnet Simulator. Once it starts, the Garnet Simulator will open up a port (port 2000 by default) and wait for Eclipse to connect to it. Eclipse will give you a pop-up saying "The selected Target Environment is being launched. Press OK when it is done launching, or Cancel to stop the debug session." Once Garnet looks like it's good and started, click OK, and Eclipse should pause on your first line of code.
<p>I had a very hard time getting this to work on my first try, though. Instead, it at first told me: "Launching (Error: Target request failed: Target is not responding (timed out).)" Apparently I'm not the only one to have this problem, but Google provided very little in the way of helpful workaround information. A big part of the problem is that all the magic is happening by opening ports. <em>Anything</em> could go wrong in this picture... a common thing that can happen is that another application may be holding the port, so PODS can no longer use it. Another common problem is that a software firewall like Symantec, McAfee or ZoneAlarm may be blocking port-to-port connections, or even the built-in firewall in Windows XP SP2. So let's consider some common tricks you can use to test this.
<p>First, if you're having trouble, I highly recommend that you download a copy of <a href="http://www.sysinternals.com/Utilities/ProcessExplorer.html">Process Explorer</a> from Sysinternals. (The download links are at the bottom of the page.) Process Explorer is a really awesome piece of software; I can't recommend it highly enough. In this case, you'll use it by looking for any PalmSim* processes that may be running. Make sure you start with <em>no</em> PalmSim* processes before you begin debugging, and that the right one starts up when you push Debug. Get the Properties of the PalmSim process by right-clicking on it. There, you should be able to see the command line which was used to start it, which may tell you something useful. You can also click on the TCP/IP tab on the Properties dialog to see what ports the process is listening on and/or using.
<p>One problem I had was that Eclipse accidentally "orphaned" my Simulator, so I had one invisible copy running in the background, hogging the ports. Once I killed that, Eclipse worked fine. Another problem I had was a software firewall blocking the port. An easy way to test this is to try to telnet to the port in question. So if on the TCP/IP tab I saw that PalmSim was "LISTENING" on Local Address "mymachine:2000", I could try "telnet mymachine 2000". If it worked, I'd expect to see a blank screen. (Press Ctrl-] to pause telnet, then type "quit" to exit.) If it didn't work, I'd expect to see something like: "Could not open connection to the host, on port 59939: Connect failed". Note that you <em>should</em> expect this if you telnet to the wrong port.
<p>If you're still uncertain as to what's going on, try rebooting, and make sure all software firewalls (including the Windows XP Firewall) are disabled.
Good luck!Dan Fabulichhttp://www.blogger.com/profile/13809786490033034477noreply@blogger.com2