Skip to main content
  1. Posts/

Day 36 - Test 000001a

OldDays seitan-spin cucumber ruby csharp

Tests, Part 1 of NaN.

Ta-dah!
Ta-dah!

Coding
#

Ok, time to start moving forward on the whole “Let’s wrap this thing in tests so we can safely replace all of the tech” thing. QA terminology varies between industries, between groups in the same industry, between people in the same group even; but I like to call this kind of stuff “arm’s length” testing. Tests run, or can run, on a separate machine from the code being tested, and are run on a “release” build. It’s pretty natural for client-server systems, where we’ll just pretend to be a normal client (for the most part).

Let’s start with a Cucumber feature file:

features/login.feature
1
2
3
4
5
6
7
Feature: Login

Scenario: Logging in a new user
Given a new random user
And a telnet connection
When I create an account and character
Then I can play the game

And some definitions for those steps:

features/steps/user_steps.rb
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Given(/^a new random user$/) do
  @user = {}
  @user[:id] = random_char_name(min_length = 4, max_length = 10)
  @user[:acct_name] = "test" + @user[:id].downcase
  @user[:acct_email] = "test" + @user[:id] + "@301days.com"
  @user[:acct_password] = "pass" + @user[:id]
  @user[:char_name] = "Dude." + @user[:id]
  @user[:char_gender] = ['1','2'].sample
  @user[:char_race] = ['I','M','L','LG','D','H','MN','B'].sample
  @user[:char_class] = ['FI','TH','WI','MA','TF'].sample
  puts "New random user: #{pp(@user)}"
end

Given(/^a telnet connection$/) do
  rand_id = rand(36**20).to_s(36)
  @connection = Net::Telnet::new("Host" => ENV['DS_HOST'],
              "Port" => 3000,
              "Output_log" => "output_#{rand_id}.log",
              "Dump_log" => "dump_#{rand_id}.log",
              "Prompt" => "Login:",
              "Telnetmode" => false,
              "Timeout" => 60,
              "Waittime" => 1)
end

When(/^I create an account and character$/) do
  @connection.cmd({"String" => "", "Match" => /Login\: /}) { |c| puts "0 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => "new", "Match" => /account\: /}) { |c| puts "1 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:acct_name], "Match" => /address\: /}) { |c| puts "2 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:acct_email], "Match" => /address\: /}) { |c| puts "3 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:acct_email], "Match" => /12\)\: /}) { |c| puts "4 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:acct_password], "Match" => /password\: /}) { |c| puts "5 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:acct_password], "Match" => /Gender\: /}) { |c| puts "6 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:char_gender], "Match" => /a Race\: /}) { |c| puts "7 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:char_race], "Match" => /Class\: /}) { |c| puts "8 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:char_class], "Match" => /\(y,n\)\: /}) { |c| puts "9 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => "n", "Match" => /character\: /}) { |c| puts "10 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
  @connection.cmd({"String" => @user[:char_name], "Match" => /Command\: /}) { |c| puts "11 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
end

Then(/^I can play the game$/) do
  @connection.cmd({"String" => "1", "Match" => / ->/}) { |c| puts "12 #{c.gsub(/\e/, "[ESC]")}" unless c.nil? }
end

and we’re off to the races, once we define random_char_name.

features/steps/user_steps.rb
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def random_char_name(min_length=4, max_length=14, prefix="Dude.")
  #public const short NAME_MIN_LENGTH = 4;
  #public const short NAME_MAX_LENGTH = 14;
  #string[] specialnames = new string[]{"orc","gnoll","goblin","skeleton","kobold","dragon","drake","griffin","manticore",
  #    "banshee","demon","bear","boar","vampire","ydnac","lars","sven","oskar","olaf",
  #    "marlis","neela","phong","ironbar","vulcan","sheriff","rolf","troll","wyvern",
  #    "ydmos","tanner","crazy.derf","trambuskar","ianta","alia","priest","statue",
  #    "shidosha","pazuzu","asmodeus","damballa","glamdrang","samael","perdurabo",
  #    "thamuz","knight","martialartist","thief","thaum","thaumaturge","wizard","fighter",
  #    "thisson"};
  #
  #string[] silly = new string[]{"pvp","lol","haha","hehe","btw","atm","jeje","rofl","roflmao","lmao","lmfao","lmho","dragonsspine",
  #    "dragonspine","nobody","somebody","anybody","account","character"};
  #
  #string acceptable = "abcdefghijklmnopqrstuvwxyz.";
  #
  specialnames = ["orc","gnoll","goblin","skeleton","kobold","dragon","drake","griffin","manticore",
                  "banshee","demon","bear","boar","vampire","ydnac","lars","sven","oskar","olaf",
                  "marlis","neela","phong","ironbar","vulcan","sheriff","rolf","troll","wyvern",
                  "ydmos","tanner","crazy.derf","trambuskar","ianta","alia","priest","statue",
                  "shidosha","pazuzu","asmodeus","damballa","glamdrang","samael","perdurabo",
                  "thamuz","knight","martialartist","thief","thaum","thaumaturge","wizard","fighter",
                  "thisson"]
  silly = ["pvp","lol","haha","hehe","btw","atm","jeje","rofl","roflmao","lmao","lmfao","lmho","dragonsspine",
      "dragonspine","nobody","somebody","anybody","account","character"]
  acceptable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".scan(/./)

To make sure I’m getting valid names, I started with code from the server’s CharGen.cs; specifically, CharacterNameDenied. But then why am I calling it with max_length of 10, and why no period in the acceptable array? Because I immediately ran into issues with the account name being rejected. A little digging found there were different rules there.

DragonsSpine/GameObjects/GameLiving/PC/Account.cs
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public static bool AccountNameDenied(string name)
{
  // check if account exists
  if (Account.AccountExists(name))
    return true;

  // check account name length
  if (name.Length < Account.ACCOUNT_MIN_LENGTH || name.Length > Account.ACCOUNT_MAX_LENGTH)
    return true;

  bool deny = false;

  string[] silly = new string[]{"pvp","lol","haha","hehe","btw","atm","jeje","rofl","roflmao","lmao","lmfao","lmho","dragonsspine",
                                "dragonspine","nobody","somebody","anybody","account"};

  string acceptable = "abcdefghijklmnopqrstuvwxyz0123456789";
DragonsSpine/GameObjects/GameLiving/PC/Account.cs
 9
10
11
12
  public const int ACCOUNT_MIN_LENGTH = 5;
  public const int ACCOUNT_MAX_LENGTH = 12;
  public const int PASSWORD_MIN_LENGTH = 4;
  public const int PASSWORD_MAX_LENGTH = 12;

So I’ll have to treat the various parts of the user a little differently. But for now, does this work?

cucumber.out
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Feature: Login

{:id=>"CnQXY",
 :acct_name=>"testcnqxy",
 :acct_email=>"testCnQXY@301days.com",
 :acct_password=>"passCnQXY",
 :char_name=>"Dude.CnQXY",
 :char_gender=>"2",
 :char_race=>"L",
 :char_class=>"FI"}
  Scenario: Logging in a new user          # features/login.feature:3
    Given a new random user                # features/steps/user_steps.rb:4
      How about CnQXY?

cucumber.out
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
      Welcome testcnqxy!
      Current Character: Dude.CnQXY Level: 3 Class: Fighter Land: Beginner's Game Map: Island of Kesmai
      
      1. Enter Game
      2. Enter Conference Room
      3. Disconnect
      4. View Account
      5. Change Protocol (normal)
      6. Change/Create/Delete Character
      
      Command: 
    Then I can play the game               # features/steps/user_steps.rb:44
      12 [ESC][2J[ESC][1;1f
      12 [ESC][2J[ESC][1;1f[ESC][3;6H. [ESC][3;8H. [ESC][3;10H. [ESC][3;12H. [ESC][3;14H. [ESC][3;16H| [ESC][3;18H  [ESC][4;6H. [ESC][4;8H. [ESC][4;10H. [ESC][4;12H. [ESC][4;14H. [ESC][4;16H[ESC][33m[][ESC][0m[ESC][4;18H[ESC][33m[][ESC][0m[ESC][5;6H[ESC][1;32m@@[ESC][0m[ESC][5;8Ho [ESC][5;10H. [ESC][5;12H. [ESC][5;14H. [ESC][5;16H. [ESC][5;18H. [ESC][6;6H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][6;8H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][6;10H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][6;12H::[ESC][6;14H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][6;16H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][6;18H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][7;6H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][7;8H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][7;10H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][7;12H::[ESC][7;14H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][7;16H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][7;18H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][8;6H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][8;8H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][8;10H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][8;12H::[ESC][8;14H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][8;16H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][8;18H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][9;6H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][9;8H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][9;10H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][9;12H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][9;14H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][9;16H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][9;18H[ESC][44m[ESC][1;37m~~[ESC][0m[ESC][2;24H A Sigur[ESC][3;10HA[ESC][3;24H*B 3 skeletons[ESC][5;6HB[ESC][6;12H^[ESC][22;1H                                                                                             [ESC][21;1H                                                                                             [ESC][20;1H                                                                                             [ESC][19;1H                                                                                             [ESC][18;1H                                                                                             [ESC][17;1H                                                                                             [ESC][16;1H                                                                                             [ESC][15;1H                                                                                             [ESC][14;1H                                                                                             [ESC][13;1H                                                                                             [ESC][12;1H                                                                                             [ESC][11;1H                                                                                             [ESC][10;1H                                                                                             [ESC][23;1H                             [ESC][23;1H [ESC][1;32mR flail[ESC][0m[ESC][24;1H                             [ESC][24;1H [ESC][1;32mL shield[ESC][0m[ESC][23;42H      [ESC][23;30H[ESC][35mHits       : 43/43    [ESC][0m[ESC][23;72H      [ESC][23;60H[ESC][35mHits Taken : 0[ESC][0m[ESC][25;42H     [ESC][25;30H[ESC][35mStamina    : 10[ESC][0m[ESC][24;42H        [ESC][24;30H[ESC][35mExperience : 1600  [ESC][0m[ESC][11;1H[ESC][21;1H ->

1 scenario (1 passed)
4 steps (4 passed)
0m21.221s

Hooray! Crude, but effective. A start.

A note about Cucumber
#

It looks like the online docs are being rewritten as we speak. I found saving a copy of the output of cucumber --help to be, well, helpful.


Playing
#

The Talos Principle
#

Second playthrough, about to try exploring the dialog trees now that I’ve done all of the main puzzles again.

Tearaway: Unfolded
#

Having played the original on the Vita, played through mostly to see the differences and to experience that world on the big screen. Even if you haven’t/won’t play it, go here for a glimpse of how it was built.


More to come
More to come

Day 36 code - tests