Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandy Clark
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandy Clark

Adding Myers Diff Rendering To The GildedRose Kata In ColdFusion

By
Published in Comments (2)

After completing my manual refactoring of the GildedRose code kata in ColdFusion, I lamented that the test results were low-fidelity. That is, they identified as Passing or Failing but gave no indication as to what within the outputs was misaligned. Then, over the weekend, I read up on string diffing algorithms and created a ColdFusion implementation of the Myers Diff algorithm. I've now added this diffing algorithm to the GildedRose test results.

View my GildedRose project for CFML on GitHub.

To quickly recap the Myers Diff algorithm, it takes two collections of strings — the "original" set and the "modified" set — and identifies the smallest number of insert, delete, and equals operations that it takes to get from the original set to the modified set. This set of operations gives us the ability to format the GildedRose test output with more affordance.

In the GildedRose test, we have a "golden master" text file. This golden master can act as our "original" input (what the tests should output if successful). Then, the output generated by the test harness becomes our "modified" input.

We can use the same Myers Diff results to render both the actual and expected outputs. The trick is to omit the delete and insert operations depending on which side of the diff we're rendering.

Here's the truncated ColdFusion code for my GildedRose test harness. The top half runs the simulation against the GildedRose.cfc code. The bottom half renders the results, comparing the simulated output to the expected "golden master" output:

<cfscript>

	// Days can be passed-in as a search parameter or an environment variable.
	days = abs( fix( val( url.days ?: server.system.environment.days ?: 30 ) ) );

	// There must be a corresponding TXT file for the expected output.
	expectedFile = "expected-#days#.txt";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	buffer = [];
	buffer.append( "OMGHAI!" );

	app = new src.GildedRose([
		new src.Item( "+5 Dexterity Vest", 10, 20 ),
		new src.Item( "Aged Brie", 2, 0 ),
		new src.Item( "Elixir of the Mongoose", 5, 7 ),
		new src.Item( "Sulfuras, Hand of Ragnaros", 0, 80 ),
		new src.Item( "Sulfuras, Hand of Ragnaros", -1, 80 ),
		new src.Item( "Backstage passes to a TAFKAL80ETC concert", 15, 20 ),
		new src.Item( "Backstage passes to a TAFKAL80ETC concert", 10, 49 ),
		new src.Item( "Backstage passes to a TAFKAL80ETC concert", 5, 49 ),
		// This conjured item does not work properly yet.
		new src.Item( "Conjured Mana Cake", 3, 6 )
	]);

	// Run simulation for days.
	for ( i = 0 ; i <= days ; i++ ) {

		buffer.append( "-------- day #i# --------" );
		buffer.append( "name, sellIn, quality" );

		for ( item in app.items ) {

			buffer.append( item.toString() );

		}

		buffer.append( "" );
		app.updateQuality();

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	myers = new lib.MyersDiff();
	utils = new lib.Utils();

	// Compare current output to expected output.
	actual = utils.fromLines( buffer ).trim();
	expected = fileRead( expectedFile, "utf-8" ).trim();
	isPassing = ( actual == expected );

	// Create line-based diff of the outputs (for visual affordance).
	diff = myers.diffElements(
		original = utils.toLines( expected ),
		modified = utils.toLines( actual )
	);

</cfscript>

<cfoutput>
<!doctype html>
<html lang="en">
<body>

	<h1>
		GildedRose Result:
		<cfif isPassing>
			<mark class="flag isPassing">TextTest Is Passing</mark>
		<cfelse>
			<mark class="flag isFailing">TextTest Is Failing</mark>
		</cfif>
	</h1>

	<div class="results">
		<section>
			<h2>
				Actual Output
			</h2>
			<cfloop array="#diff.operations#" item="operation">

				<cfswitch expression="#operation.type#">
					<cfcase value="delete">
						<!--- Ignore deletes (these are the "expected" lines). --->
					</cfcase>
					<cfcase value="insert">
						<pre><ins>#encodeForHtml( operation.value )#<br /></ins></pre>
					</cfcase>
					<cfdefaultcase>
						<pre>#encodeForHtml( operation.value )#<br /></pre>
					</cfdefaultcase>
				</cfswitch>

			</cfloop>
		</section>
		<section>
			<h2>
				Expected Output
			</h2>
			<cfloop array="#diff.operations#" item="operation">

				<cfswitch expression="#operation.type#">
					<cfcase value="delete">
						<pre><del>#encodeForHtml( operation.value )#<br /></del></pre>
					</cfcase>
					<cfcase value="insert">
						<!--- Ignore inserts (these are the "modified" lines). --->
					</cfcase>
					<cfdefaultcase>
						<pre>#encodeForHtml( operation.value )#<br /></pre>
					</cfdefaultcase>
				</cfswitch>

			</cfloop>
		</section>
	</div>

</body>
</html>
</cfoutput>

If I go into the GildedRose.cfc ColdFusion component and purposefully corrupt some of the logic, altering the qaulity degradation rules, the problematic lines are now clearly identifies in the test output (truncated to show some differences):

In my previous post on the Myers Diff algorithm, I talked about doing a sub-diff on individual lines in order to show specific insertions and deletions. For the sake of simplicity, I've opted not to do that in this project. But, I think that adding the high-level Myers Diff makes a huge difference in terms of test usability.

Want to use code from this post? Check out the license.

Reader Comments

16,154 Comments

It's just really fun to see things come together in unexpected ways. Learning for the sake of learning is still valuable.... I think.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel
Managed hosting services provided by:
xByte Cloud Logo