Skip to content

Commit

Permalink
[Compiler UT] unit test string (#1199)
Browse files Browse the repository at this point in the history
* unit test string

* Apply suggestions from code review

---------

Co-authored-by: Shargon <[email protected]>
  • Loading branch information
Jim8y and shargon authored Oct 7, 2024
1 parent 112a30c commit f59bbbc
Show file tree
Hide file tree
Showing 7 changed files with 474 additions and 311 deletions.
50 changes: 0 additions & 50 deletions src/Neo.Compiler.CSharp/MethodConvert/System/SystemCall.Enum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,56 +257,6 @@ private static void HandleEnumTryParseIgnoreCase(MethodConvert methodConvert, Se
methodConvert.AddInstruction(OpCode.RET);
}

private static void ConvertToUpper(MethodConvert methodConvert)
{
var loopStart = new JumpTarget();
var loopEnd = new JumpTarget();
var charIsLower = new JumpTarget();
methodConvert.Push(""); // Create an empty ByteString

// methodConvert.AddInstruction(OpCode.LDARG0); // Load the string | arr str
methodConvert.AddInstruction(OpCode.PUSH0); // Push the initial index (0) arr str 0
loopStart.Instruction = methodConvert.AddInstruction(OpCode.NOP);

methodConvert.AddInstruction(OpCode.DUP); // Duplicate the index
methodConvert.AddInstruction(OpCode.LDARG0); // Load the string
methodConvert.AddInstruction(OpCode.SIZE); // Get the length of the string
methodConvert.AddInstruction(OpCode.LT); // Check if index < length
methodConvert.Jump(OpCode.JMPIFNOT, loopEnd); // If not, exit the loop

methodConvert.AddInstruction(OpCode.DUP); // Duplicate the index | arr str 0 0
methodConvert.AddInstruction(OpCode.LDARG0); // Load the string
methodConvert.Swap();
methodConvert.AddInstruction(OpCode.PICKITEM); // Get the character at the current index
methodConvert.AddInstruction(OpCode.DUP); // Duplicate the character
methodConvert.Push((ushort)'a'); // Push 'a'
methodConvert.Push((ushort)'z' + 1); // Push 'z' + 1
methodConvert.AddInstruction(OpCode.WITHIN); // Check if character is within 'a' to 'z'
methodConvert.Jump(OpCode.JMPIF, charIsLower); // If true, jump to charIsLower
methodConvert.Rot();
methodConvert.Swap();
methodConvert.AddInstruction(OpCode.CAT); // Append the original character to the array
methodConvert.Swap();
methodConvert.Inc();
methodConvert.Jump(OpCode.JMP, loopStart); // Jump to the start of the loop

charIsLower.Instruction = methodConvert.AddInstruction(OpCode.NOP);
methodConvert.Push((ushort)'a'); // Push 'a'
methodConvert.AddInstruction(OpCode.SUB); // Subtract 'a' from the character
methodConvert.Push((ushort)'A'); // Push 'A'
methodConvert.AddInstruction(OpCode.ADD); // Add 'A' to the result
methodConvert.Rot();
methodConvert.Swap();
methodConvert.AddInstruction(OpCode.CAT); // Append the upper case character to the array
methodConvert.Swap();
methodConvert.Inc();
methodConvert.Jump(OpCode.JMP, loopStart); // Jump to the start of the loop

loopEnd.Instruction = methodConvert.AddInstruction(OpCode.NOP);
methodConvert.Drop();
methodConvert.ChangeType(VM.Types.StackItemType.ByteString); // Convert the array to a byte string
}

private static void HandleEnumIsDefinedByName(MethodConvert methodConvert, SemanticModel model, IMethodSymbol symbol,
ExpressionSyntax? instanceExpression, IReadOnlyList<SyntaxNode>? arguments)
{
Expand Down
498 changes: 265 additions & 233 deletions src/Neo.Compiler.CSharp/MethodConvert/System/SystemCall.String.cs

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions src/Neo.SmartContract.Analyzer/StringMethodUsageAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ public class StringMethodUsageAnalyzer : DiagnosticAnalyzer
"IndexOfAny", "Insert", "Intern", "IsInterned",
"IsNormalized", "Join", "LastIndexOf", "LastIndexOfAny",
"Normalize", "PadLeft", "PadRight", "Remove",
"Replace", "Split",
"ToCharArray", "ToLower", "ToLowerInvariant",
"ToUpper", "ToUpperInvariant", "TrimEnd",
"TrimStart"
"Replace", "Split", "ToCharArray", "ToLowerInvariant",
"ToUpperInvariant", "TrimEnd", "TrimStart"
};

private static readonly DiagnosticDescriptor Rule = new(
Expand Down
40 changes: 40 additions & 0 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_String.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,45 @@ public static string TestTrim(string str)
{
return str.Trim();
}

public static char TestPickItem(string s, int index)
{
return s[index];
}

public static string TestSubstringToEnd(string s, int startIndex)
{
return s.Substring(startIndex);
}

public static string TestConcat(string? s1, string? s2)
{
return string.Concat(s1, s2);
}

public static int TestIndexOfChar(string s, char c)
{
return s.IndexOf(c);
}

public static string TestToLower(string s)
{
return s.ToLower();
}

public static string TestToUpper(string s)
{
return s.ToUpper();
}

public static string TestTrimChar(string s, char trimChar)
{
return s.Trim(trimChar);
}

public static int TestLength(string s)
{
return s.Length;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ public abstract class Contract_String(Neo.SmartContract.Testing.SmartContractIni
{
#region Compiled data

public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_String"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""testMain"",""parameters"":[],""returntype"":""Void"",""offset"":0,""safe"":false},{""name"":""testEqual"",""parameters"":[],""returntype"":""Void"",""offset"":82,""safe"":false},{""name"":""testSubstring"",""parameters"":[],""returntype"":""Void"",""offset"":127,""safe"":false},{""name"":""testEmpty"",""parameters"":[],""returntype"":""String"",""offset"":163,""safe"":false},{""name"":""testIsNullOrEmpty"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Boolean"",""offset"":166,""safe"":false},{""name"":""testEndWith"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Boolean"",""offset"":181,""safe"":false},{""name"":""testContains"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Boolean"",""offset"":220,""safe"":false},{""name"":""testIndexOf"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Integer"",""offset"":237,""safe"":false},{""name"":""testInterpolatedStringHandler"",""parameters"":[],""returntype"":""String"",""offset"":252,""safe"":false},{""name"":""testTrim"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""String"",""offset"":579,""safe"":false}],""events"":[]},""permissions"":[{""contract"":""0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0"",""methods"":[""itoa"",""memorySearch""]},{""contract"":""0xda65b600f7124ce6c79950c1772a36403104f2be"",""methods"":[""currentHash"",""getBlock""]}],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");
public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_String"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""testMain"",""parameters"":[],""returntype"":""Void"",""offset"":0,""safe"":false},{""name"":""testEqual"",""parameters"":[],""returntype"":""Void"",""offset"":82,""safe"":false},{""name"":""testSubstring"",""parameters"":[],""returntype"":""Void"",""offset"":127,""safe"":false},{""name"":""testEmpty"",""parameters"":[],""returntype"":""String"",""offset"":163,""safe"":false},{""name"":""testIsNullOrEmpty"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Boolean"",""offset"":166,""safe"":false},{""name"":""testEndWith"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Boolean"",""offset"":181,""safe"":false},{""name"":""testContains"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Boolean"",""offset"":220,""safe"":false},{""name"":""testIndexOf"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""Integer"",""offset"":237,""safe"":false},{""name"":""testInterpolatedStringHandler"",""parameters"":[],""returntype"":""String"",""offset"":252,""safe"":false},{""name"":""testTrim"",""parameters"":[{""name"":""str"",""type"":""String""}],""returntype"":""String"",""offset"":579,""safe"":false},{""name"":""testPickItem"",""parameters"":[{""name"":""s"",""type"":""String""},{""name"":""index"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":646,""safe"":false},{""name"":""testSubstringToEnd"",""parameters"":[{""name"":""s"",""type"":""String""},{""name"":""startIndex"",""type"":""Integer""}],""returntype"":""String"",""offset"":653,""safe"":false},{""name"":""testConcat"",""parameters"":[{""name"":""s1"",""type"":""String""},{""name"":""s2"",""type"":""String""}],""returntype"":""String"",""offset"":664,""safe"":false},{""name"":""testIndexOfChar"",""parameters"":[{""name"":""s"",""type"":""String""},{""name"":""c"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":686,""safe"":false},{""name"":""testToLower"",""parameters"":[{""name"":""s"",""type"":""String""}],""returntype"":""String"",""offset"":695,""safe"":false},{""name"":""testToUpper"",""parameters"":[{""name"":""s"",""type"":""String""}],""returntype"":""String"",""offset"":743,""safe"":false},{""name"":""testTrimChar"",""parameters"":[{""name"":""s"",""type"":""String""},{""name"":""trimChar"",""type"":""Integer""}],""returntype"":""String"",""offset"":791,""safe"":false},{""name"":""testLength"",""parameters"":[{""name"":""s"",""type"":""String""}],""returntype"":""Integer"",""offset"":846,""safe"":false}],""events"":[]},""permissions"":[{""contract"":""0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0"",""methods"":[""itoa"",""memorySearch""]},{""contract"":""0xda65b600f7124ce6c79950c1772a36403104f2be"",""methods"":[""currentHash"",""getBlock""]}],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");

/// <summary>
/// Optimization: "All"
/// </summary>
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS+8gQxQDYqd8FQmcfmTBL3ALZl2ghnZXRCbG9jawEAAQ++8gQxQDYqd8FQmcfmTBL3ALZl2gtjdXJyZW50SGFzaAAAAQ/A7znO4OTpJcbCoGp54UQN2G/OrARpdG9hAQABD8DvOc7g5OklxsKgannhRA3Yb86sDG1lbW9yeVNlYXJjaAIAAQ8AAP2GAlcDAAwETWFya3AMAHE3AQA3AAAUznIMB0hlbGxvLCBoiwwBIItpiwwXISBDdXJyZW50IHRpbWVzdGFtcCBpcyCLajcCAIsMAS6L2yhBz+dHlkBXAgAMBWhlbGxvcAwFaGVsbG9xaGmXJAsMBUZhbHNlIggMBFRydWVBz+dHlkBXAQAMCDAxMjM0NTY3cGgRS8pLn4xBz+dHlmgRFIxBz+dHlkAMAEBXAAF4StgkBsoQs0BFCEBXAAEMBXdvcmxkeErKUUrKShNSUJ9KECwIRUVFRQlAE1JTjNsol0BXAAEMBXdvcmxkeDcDABC4QFcAAQwFd29ybGR4NwMAQFcEAAQAAKDexa3JNTYAAAAAAAAAcAwiTlhWN1poSGl5TTFhSFh3cFZzUlpDNkJ3TkZQMmpnaFhBcXEMAwECA9swcgwHU0J5dGU6IADWNwIAiwwILCBCeXRlOiCLACo3AgCLDAosIFVTaG9ydDogiwHoAzcCAIsMAiwgi9soDAZVSW50OiACQEIPADcCAIsMCSwgVUxvbmc6IIsDABCl1OgAAAA3AgCLDAIsIIvbKIvbKAwMQmlnSW50ZWdlcjogaDcCAIsMCCwgQ2hhcjogiwBB2yiLDAosIFN0cmluZzogiwwFSGVsbG+LDAIsIIvbKIvbKAwJRUNQb2ludDogaYsMDiwgQnl0ZVN0cmluZzogiwwNU3lzdGVtLkJ5dGVbXYsMCCwgQm9vbDogiwgmCgwEVHJ1ZSIJDAVGYWxzZYvbKIvbKHNrQFcDAXjKcBBxaJ1yaWi1JhV4ac5KGR67UAAgl6wmB2mccSLqamm3JhV4as5KGR67UAAgl6wmB2qdciLqeGlqaZ+cjEAbrJCF"));
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS+8gQxQDYqd8FQmcfmTBL3ALZl2ghnZXRCbG9jawEAAQ++8gQxQDYqd8FQmcfmTBL3ALZl2gtjdXJyZW50SGFzaAAAAQ/A7znO4OTpJcbCoGp54UQN2G/OrARpdG9hAQABD8DvOc7g5OklxsKgannhRA3Yb86sDG1lbW9yeVNlYXJjaAIAAQ8AAP1UA1cDAAwETWFya3AMAHE3AQA3AAAUznIMB0hlbGxvLCBoiwwBIItpiwwXISBDdXJyZW50IHRpbWVzdGFtcCBpcyCLajcCAIsMAS6L2yhBz+dHlkBXAgAMBWhlbGxvcAwFaGVsbG9xaGmXJAsMBUZhbHNlIggMBFRydWVBz+dHlkBXAQAMCDAxMjM0NTY3cGgRS8pLn4xBz+dHlmgRFIxBz+dHlkAMAEBXAAF4StgkBsoQs0BFCEBXAAEMBXdvcmxkeErKUUrKShNSUJ9KECwIRUVFRQlAE1JTjNsol0BXAAEMBXdvcmxkeDcDABC4QFcAAQwFd29ybGR4NwMAQFcEAAQAAKDexa3JNTYAAAAAAAAAcAwiTlhWN1poSGl5TTFhSFh3cFZzUlpDNkJ3TkZQMmpnaFhBcXEMAwECA9swcgwHU0J5dGU6IADWNwIAiwwILCBCeXRlOiCLACo3AgCLDAosIFVTaG9ydDogiwHoAzcCAIsMAiwgi9soDAZVSW50OiACQEIPADcCAIsMCSwgVUxvbmc6IIsDABCl1OgAAAA3AgCLDAIsIIvbKIvbKAwMQmlnSW50ZWdlcjogaDcCAIsMCCwgQ2hhcjogiwBB2yiLDAosIFN0cmluZzogiwwFSGVsbG+LDAIsIIvbKIvbKAwJRUNQb2ludDogaYsMDiwgQnl0ZVN0cmluZzogiwwNU3lzdGVtLkJ5dGVbXYsMCCwgQm9vbDogiwgmCgwEVHJ1ZSIJDAVGYWxzZYvbKIvbKHNrQFcDAXjKcBBxaJ1yaWi1JhV4ac5KGR67UAAgl6wmB2mccSLqamm3JhV4as5KGR67UAAgl6wmB2qdciLqeGlqaZ+cjEBXAAJ4ec5AVwACeHlLykufjEBXAAJ5eErYJgVFDABQStgmBUUMAItAVwACeXg3AwBAVwABDAAQSnjKtSYiSnhQzkoAQQBbuyQJUVCLUJwi6QBBnwBhnlFQi1CcItxF2yhAVwABDAAQSnjKtSYiSnhQzkoAYQB7uyQJUVCLUJwi6QBhnwBBnlFQi1CcItxF2yhAVwMCeXjKcBBxaJ1yRWlotSYOeGnOebMmB2mccSLxamm3Jg54as55syYHap1yIvF4aWppn5yMQFcAAXjKQGi4rDg="));

#endregion

#region Unsafe methods

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testConcat")]
public abstract string? TestConcat(string? s1, string? s2);

/// <summary>
/// Unsafe method
/// </summary>
Expand Down Expand Up @@ -51,6 +57,12 @@ public abstract class Contract_String(Neo.SmartContract.Testing.SmartContractIni
[DisplayName("testIndexOf")]
public abstract BigInteger? TestIndexOf(string? str);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testIndexOfChar")]
public abstract BigInteger? TestIndexOfChar(string? s, BigInteger? c);

/// <summary>
/// Unsafe method
/// </summary>
Expand All @@ -63,24 +75,60 @@ public abstract class Contract_String(Neo.SmartContract.Testing.SmartContractIni
[DisplayName("testIsNullOrEmpty")]
public abstract bool? TestIsNullOrEmpty(string? str);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testLength")]
public abstract BigInteger? TestLength(string? s);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testMain")]
public abstract void TestMain();

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testPickItem")]
public abstract BigInteger? TestPickItem(string? s, BigInteger? index);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testSubstring")]
public abstract void TestSubstring();

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testSubstringToEnd")]
public abstract string? TestSubstringToEnd(string? s, BigInteger? startIndex);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testToLower")]
public abstract string? TestToLower(string? s);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testToUpper")]
public abstract string? TestToUpper(string? s);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testTrim")]
public abstract string? TestTrim(string? str);

/// <summary>
/// Unsafe method
/// </summary>
[DisplayName("testTrimChar")]
public abstract string? TestTrimChar(string? s, BigInteger? trimChar);

#endregion

}
117 changes: 117 additions & 0 deletions tests/Neo.Compiler.CSharp.UnitTests/UnitTest_String.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.SmartContract.Testing;
using System.Collections.Generic;
using Neo.SmartContract.Testing.Exceptions;

namespace Neo.Compiler.CSharp.UnitTests
{
Expand Down Expand Up @@ -144,5 +145,121 @@ public void Test_TestTrim()
Assert.AreEqual("Mix of Whitespace", Contract.TestTrim(" \t \n \r Mix of Whitespace \r \n \t "));
AssertGasConsumed(1180710);
}

[TestMethod]
public void Test_TestPickItem()
{
Assert.AreEqual('e', Contract.TestPickItem("Hello", 1));
AssertGasConsumed(1049250);

Assert.AreEqual('d', Contract.TestPickItem("World", 4));
AssertGasConsumed(1049250);

// Test invalid index
Assert.ThrowsException<TestException>(() => Contract.TestPickItem("Test", 5));
}

[TestMethod]
public void Test_TestSubstringToEnd()
{
Assert.AreEqual("World", Contract.TestSubstringToEnd("Hello World", 6));
AssertGasConsumed(1109250);

Assert.AreEqual("", Contract.TestSubstringToEnd("Test", 4));
AssertGasConsumed(1109250);

// Test invalid start index
Assert.ThrowsException<TestException>(() => Contract.TestSubstringToEnd("Test", 5));
}

[TestMethod]
public void Test_TestConcat()
{
Assert.AreEqual("HelloWorld", Contract.TestConcat("Hello", "World"));
AssertGasConsumed(1109400);

Assert.AreEqual("Test", Contract.TestConcat("Test", null));
AssertGasConsumed(1109490);

Assert.AreEqual("Test", Contract.TestConcat(null, "Test"));
AssertGasConsumed(1109490);

Assert.AreEqual("", Contract.TestConcat(null, null));
AssertGasConsumed(1109580);

// Test with empty strings
Assert.AreEqual("", Contract.TestConcat("", ""));
AssertGasConsumed(1109400);
}

[TestMethod]
public void Test_TestIndexOfChar()
{
Assert.AreEqual(1, Contract.TestIndexOfChar("Hello", 'e'));
AssertGasConsumed(2032320);

Assert.AreEqual(-1, Contract.TestIndexOfChar("World", 'x'));
AssertGasConsumed(2032320);

// Test with empty string
Assert.AreEqual(-1, Contract.TestIndexOfChar("", 'a'));
AssertGasConsumed(2032320);
}

[TestMethod]
public void Test_TestToLower()
{
Assert.AreEqual("hello world", Contract.TestToLower("Hello World"));
AssertGasConsumed(2008350);

Assert.AreEqual("123", Contract.TestToLower("123"));
AssertGasConsumed(1488390);

// Test with already lowercase string
Assert.AreEqual("lowercase", Contract.TestToLower("lowercase"));
AssertGasConsumed(1877550);
}

[TestMethod]
public void Test_TestToUpper()
{
Assert.AreEqual("HELLO WORLD", Contract.TestToUpper("Hello World"));
AssertGasConsumed(2011590);

Assert.AreEqual("123", Contract.TestToUpper("123"));
AssertGasConsumed(1488390);

// Test with already uppercase string
Assert.AreEqual("UPPERCASE", Contract.TestToUpper("UPPERCASE"));
AssertGasConsumed(1877550);
}

[TestMethod]
public void Test_TestTrimChar()
{
Assert.AreEqual("Hello World", Contract.TestTrimChar("***Hello World***", '*'));
AssertGasConsumed(1134300);

Assert.AreEqual("Test", Contract.TestTrimChar("Test", '*'));
AssertGasConsumed(1115580);

// Test with string containing only trim characters
Assert.AreEqual("", Contract.TestTrimChar("****", '*'));
AssertGasConsumed(1123260);
}

[TestMethod]
public void Test_TestLength()
{
Assert.AreEqual(11, Contract.TestLength("Hello World"));
AssertGasConsumed(1047360);

Assert.AreEqual(0, Contract.TestLength(""));
AssertGasConsumed(1047360);

// Test with very long string
Assert.AreEqual(1000, Contract.TestLength(new string('a', 1000)));
AssertGasConsumed(1062480);
}
}
}
Loading

0 comments on commit f59bbbc

Please sign in to comment.