HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
unclebob
fitnesse
Java2009–2026
unclebob / fitnesse

fitnesse

FitNesse -- The Acceptance Test Wiki

19Distinct people
20Mentions
0Explicit recommenders
2.2KGitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

FitNesse -- The Acceptance Test Wiki

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

No recommendation phrase was found in the indexed comments.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

No other repository is repeatedly linked in the same discussions.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 3:56:07 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year20 dated mentions

Observed mentions: 2009-02-12 04:36:24 UTC to 2026-08-24 13:07:26 UTC.

0123452009-01-01 00:00:00 UTC to 2010-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments20092010-01-01 00:00:00 UTC to 2011-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2011-01-01 00:00:00 UTC to 2012-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2012-01-01 00:00:00 UTC to 2013-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2013-01-01 00:00:00 UTC to 2014-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2014-01-01 00:00:00 UTC to 2015-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2015-01-01 00:00:00 UTC to 2016-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2016-01-01 00:00:00 UTC to 2017-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments20172018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 5 mentions; 0 submissions, 5 comments2022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 4 mentions; 0 submissions, 4 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20092009-01-01 00:00:00 UTC2010-01-01 00:00:00 UTC202
20102010-01-01 00:00:00 UTC2011-01-01 00:00:00 UTC000
20112011-01-01 00:00:00 UTC2012-01-01 00:00:00 UTC101
20122012-01-01 00:00:00 UTC2013-01-01 00:00:00 UTC000
20132013-01-01 00:00:00 UTC2014-01-01 00:00:00 UTC000
20142014-01-01 00:00:00 UTC2015-01-01 00:00:00 UTC101
20152015-01-01 00:00:00 UTC2016-01-01 00:00:00 UTC000
20162016-01-01 00:00:00 UTC2017-01-01 00:00:00 UTC000
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC000
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC000
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC000
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC101
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC505
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC303
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC101
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC101
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC404
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC101

What Hacker News says

20 sources
I was completely ready to agree with you, but the code that I browsed actually looked really good. https://github.com/unclebob/fitnesse/blob/master/src/fitness... Yup, OK, got it. The FitnessContext looks a bit rough, but no big deal. https://github.com/unclebob/fitnesse/blob/master/src/fitness... That's completely readable, I get it. https://github.com/unclebob/fitnesse/blob/master/src/fitness... Again, looks fine. The main issue with all of these files is that there is quite a bit of boilerplate code, but that's Java's fault, not the code's fault. I'm sorry, but I disagree. Looking for the most substantial pieces of code that I could find, and they look really good. I'm sure there are some little utils or something that look strange out of context, but I would be thrilled if I were called to work on legacy code and it was this nice.
In Clean Code vs. A Philosophy Of Software Design
(I notified Martin and the FitNesse user mailing list about this back in 2010. I assume their threat model is that the default hash function is about the same as a closed office door - a request to stay out, or at least knock first - rather than a strong preventative measure.) In this comment I'll demonstrate that 1) there is a hash function, 2) in use, 3) with a successful preimage attack. "Uncle" Bob Martin's "FitNesse", see https://en.wikipedia.org/wiki/FitNesse and http://fitnesse.org/ , uses its own hash function, at https://github.com/unclebob/fitnesse/blob/master/src/fitness... . The Python equivalent is: import base64 repetitions = 3 lock_bytes = b"Like a long-leggedfly upon the stream\nHis mind moves upon silence." def encrypt(value): result = [0] * 15 for i, byte in enumerate(lock_bytes):
Read the rest
result[i%15] += (byte + repetitions*ord(value[i%len(value)])) s = bytes([(i % 256) for i in result]) return base64.b64encode(s) Here's an example: >>> encrypt("Swordfish") b'YW5a6EcE5U6LOQbfTb+M' >>> encrypt("Too many secrets") b'2fNpXgkyNCrUJArsdIat' This is a very weak hash function. With a bit of tweaking the above function is: init_result = [0] * 15 for i, byte in enumerate(lock_bytes): init_result[i%15] += byte init_result = [value % 256 for value in init_result] # init_result = [163, 198, 30, 15, 204, 206, 32, 11, 156, 182, 183, 219, 109, 169, 163] def encrypt(value): N = len(value) result = [None] * 15 for b in range(15): result[b] = (init_result[b] + (3 * sum(ord(value[i%N]) for i in range(b, 66, 15)))) % 256 return base64.b64encode(bytes(result)) where the lock_bytes is not used during the encoding. To simplify preimage construction, the generated preimage will have 66 bytes, matching len(lock_bytes). We need to find values for positions 0, 15, 30, 45, and 60 such that their value, plus the initial value for hash[0], adds up to the expected value for hash[0], and so on. One such solution is: import itertools def get_preimage(expected): result = base64.b64decode(expected) assert len(result) == 15, result input_bytes = bytearray(b"A" * 66) # start with all 'A's for b, needed_value in enumerate(result): offsets = list(range(b, 66, 15)) needed_value = (needed_value - init_result[b] + 256) % 256 # Adjust the characters one-by-one until finding a match. # This works because 3 and 256 are relatively prime, finding # a solution within 256 update attempts. # (There's probably a more elegant solution.) positions = range(b, 66, 15) for change_pos in itertools.cycle(positions): value = (3*sum(input_bytes[pos] for pos in positions)) % 256 if value == needed_value: break input_bytes[change_pos] += 1 return "".join(map(chr, input_bytes)) To demonstrate: >>> h = encrypt("Swordfish") >>> h b'YW5a6EcE5U6LOQbfTb+M' >>> s = get_preimage(h) >>> s 'brkdojfqjarkhmibrkdojfpi`qkhmibrjdojfpi`qkhlibqjdnjepi`qkhlhbqjcnj' >>> encrypt(s) b'YW5a6EcE5U6LOQbfTb+M' >>> encrypt(s) == h True For example, the documentation at http://fitnesse.org/FitNesse.UserGuide.AdministeringFitNesse... shows how to generate password hashes. I'll follow those steps: % env CLASSPATH=fitnesse-standalone.jar java fitnesse.authentication.Password Leonardo Be advised, the password will be visible as it is typed. enter password for Leonardo: katana confirm password: katana password saved in passwords.txt % cat passwords.txt !fitnesse.authentication.HashingCipher Leonardo:rMN4+vDv6OWafpHZNYOh (The documentation says "katana" hashes to "VEN4CfBvGCSafZDZNIKh", which is incorrect.) I'll now use the above Python code to verify that it matches the FitNesse hash, and that it generates a successful preimage: >>> encrypt("katana") b'rMN4+vDv6OWafpHZNYOh' >>> get_preimage(b"rMN4+vDv6OWafpHZNYOh") 'ggmeiifhkfhkfhkgfmeiifhkfhkfhkgfleiifgjfgjfgjgfleihfgjfgjfgjgflehh' >>> encrypt("ggmeiifhkfhkfhkgfmeiifhkfhkfhkgfleiifgjfgjfgjgfleihfgjfgjfgjgflehh") b'rMN4+vDv6OWafpHZNYOh' Remember, leave cryptographic hashing to the experts. ;) It was even worse back in 2010 because the FitNesse web server allowed a path traversal attack, making it possible to request "../passwords.txt" and get the default password file for the server. This has since been fixed.
In Is there a feasible preimage attack for any hash function today?
I fell in love with that framework back in the 00s so much that I bought the book, but pragmatically unless the domain expert is willing to write the tests, or specify requirements, it's a non-starter. I also think that Cucumber and its derivatives are the modern version of Fitnesse, but suffer from the same "requirements aren't Agile" problem for sure Also, it took an inordinate number of clicks to find the modern source code, which has an unusual license: https://github.com/unclebob/fitnesse/blob/master/LICENSE.txt ("Common Public License Version 1.0"); a cursory search seems to imply it's the precursor to the Eclipse Public License, but I still can't recall the last time I saw CPL in use
In Fit: Framework for Integrated Test
> First, the class name, SetupTeardownIncluder, is dreadful. It is, at least, a noun phrase, as all class names should be. But it's a nouned verb phrase, the strangled kind of class name you invariably get when you're working in strictly object-oriented code, where everything has to be a class, but sometimes the thing you really need is just one simple gosh-danged function. Moving from Java as my only language to JavaScript and Rust, this point was driven home in spades. A programming language can be dysfunctional, causing its users to implement harmful practices. SetupDeardownIncluder is a good example of the kind of code you get when there are no free-standing functions. It's also one path on the slippery slope to FactoryFactoryManager code. The main problem is that the intent of the code isn't even clear. Compare it with something you
Read the rest
might write in Rust: fn render(page_data: &PageData) -> String { // render the page } If you saw that function at the top line of file, or if you saw render.rs in a directory listing, you'd have a pretty good idea of what's going on before you even dug into the code. Just randomly searching the Fitness repo, there's this: // Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved. // Released under the terms of the GNU General Public License version 2 or later. package fitnesse.html; public class HtmlPageFactory { public HtmlPage newPage() { return new HtmlPage(); } public String toString() { return getClass().getName(); } } https://github.com/unclebob/fitnesse/blob/3bec390e6f8e9e3411... Really, how does this file even justify its existence? Its a subsidy courtesy of language design decisions made a long time ago.
In It's probably time to stop recommending Clean Code (2020)
6) The password hashing scheme was trivially broken, that is, given the hash I could construct a password which generated the same hash. Take a look - it still uses the same hash algorithm! https://github.com/unclebob/fitnesse/blob/master/src/fitness... What fresh hell is this? private byte[] encrypt(byte[] lock, byte[] key) { int keyIndex = 0; for (int i = 0; i < lock.length; i++) { byte lockByte = lock[i]; byte keyByte = key[keyIndex++]; lock[i] = (byte) (lockByte + keyByte); if (keyIndex == key.length) keyIndex = 0; } return lock; }
In Uncle Bob and Silver Bullets (2017)
I got fed up with Robert Martin years ago. 11 years ago I wrote an essay on "Problems with TDD" - http://www.dalkescientific.com/writings/diary/archive/2009/1... . Martin was rather against my viewpoint. So, what's an example of TDD "done right"? It should be his "FitNesse" program, right? (http://fitnesse.org/ ) Which means that finding problems in that code should point out areas where TDD is insufficient, right? It contains its own web server, so I looked at just that part. 1) There was a directory traversal attack, in several places. This gave access to my /etc/passwd curl 'http://localhost:8080/files/../../../../../../etc/passwd' 2) This GET request deleted a file: curl 'http://localhost:8080/files/?responder=deleteFile&filename=../../../../../../Users…' 3) This uploaded a file to an arbitrary location:
Read the rest
POST /files/../../ HTTP/1.1 containing a header with Content-Disposition: form-data; name="file"; filename="I_escaped.txt" 4) There was a non-persistent cross-site scripting vulnerability due to incorrectly escaped HTML: http://localhost:8080/files?responder=%3Cscript%3Ealert%28%22hi!%22%29%3C/script%3E 5) An uploaded file with an embedded NUL in filename would result in an infinite loop in the server: Content-Disposition: form-data; name="file"; filename="\0foo.txt" 6) The password hashing scheme was trivially broken, that is, given the hash I could construct a password which generated the same hash. Take a look - it still uses the same hash algorithm! https://github.com/unclebob/fitnesse/blob/master/src/fitness... These meant the system was totally p0wnable. And I found a few public servers using FitNesse as the web server. I reported all of these years ago, and at least some of them were fixed. If these security issues are still present now, there's been plenty of time to fix them. My analysis helped confirm my view that TDD generates happy-path tests, and strengthen my complaint that TDD, at least in the "red-green-refactor" formalism, ignores the rest of the testing/design that needs to be done even at that development stage where TDD is most effective.
In Uncle Bob and Silver Bullets (2017)

Showing the 15 most recent of 20 sources. Enable JavaScript to search and browse every indexed mention.

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.