Add frozen checks in Ruby (#5726)

* add frozen checks

* Use rb_check_frozen

* Correct assertion on frozen error message

The second argument for the method assert_raise is the message
to show when the assertion fails. It does not check the error
object's message.
Add an additional assertion that does check the error's message.

* do frozen check first
diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb
index 269c9ee..db97614 100644
--- a/ruby/tests/basic.rb
+++ b/ruby/tests/basic.rb
@@ -357,5 +357,22 @@
       assert_equal nil, file_descriptor.name
       assert_equal :proto3, file_descriptor.syntax
     end
+
+    def test_map_freeze
+      m = proto_module::MapMessage.new
+      m.map_string_int32['a'] = 5
+      m.map_string_msg['b'] = proto_module::TestMessage2.new
+
+      m.map_string_int32.freeze
+      m.map_string_msg.freeze
+
+      assert m.map_string_int32.frozen?
+      assert m.map_string_msg.frozen?
+
+      assert_raise(FrozenError) { m.map_string_int32['foo'] = 1 }
+      assert_raise(FrozenError) { m.map_string_msg['bar'] = proto_module::TestMessage2.new }
+      assert_raise(FrozenError) { m.map_string_int32.delete('a') }
+      assert_raise(FrozenError) { m.map_string_int32.clear }
+    end
   end
 end
diff --git a/ruby/tests/common_tests.rb b/ruby/tests/common_tests.rb
index 12388c6..638ad76 100644
--- a/ruby/tests/common_tests.rb
+++ b/ruby/tests/common_tests.rb
@@ -1269,6 +1269,39 @@
     assert proto_module::TestMessage.new != nil
   end
 
+  def test_freeze
+    m = proto_module::TestMessage.new
+    m.optional_int32 = 10
+    m.freeze
+
+    frozen_error = assert_raise(FrozenError) { m.optional_int32 = 20 }
+    assert_equal "can't modify frozen #{proto_module}::TestMessage", frozen_error.message
+    assert_equal 10, m.optional_int32
+    assert_equal true, m.frozen?
+
+    assert_raise(FrozenError) { m.optional_int64 = 2 }
+    assert_raise(FrozenError) { m.optional_uint32 = 3 }
+    assert_raise(FrozenError) { m.optional_uint64 = 4 }
+    assert_raise(FrozenError) { m.optional_bool = true }
+    assert_raise(FrozenError) { m.optional_float = 6.0 }
+    assert_raise(FrozenError) { m.optional_double = 7.0 }
+    assert_raise(FrozenError) { m.optional_string = '8' }
+    assert_raise(FrozenError) { m.optional_bytes = nil }
+    assert_raise(FrozenError) { m.optional_msg = proto_module::TestMessage2.new }
+    assert_raise(FrozenError) { m.optional_enum = :A }
+    assert_raise(FrozenError) { m.repeated_int32 = 1 }
+    assert_raise(FrozenError) { m.repeated_int64 = 2 }
+    assert_raise(FrozenError) { m.repeated_uint32 = 3 }
+    assert_raise(FrozenError) { m.repeated_uint64 = 4 }
+    assert_raise(FrozenError) { m.repeated_bool = true }
+    assert_raise(FrozenError) { m.repeated_float = 6.0 }
+    assert_raise(FrozenError) { m.repeated_double = 7.0 }
+    assert_raise(FrozenError) { m.repeated_string = '8' }
+    assert_raise(FrozenError) { m.repeated_bytes = nil }
+    assert_raise(FrozenError) { m.repeated_msg = proto_module::TestMessage2.new }
+    assert_raise(FrozenError) { m.repeated_enum = :A }
+  end
+  
   def test_eq
     m1 = proto_module::TestMessage.new(:optional_string => 'foo', :repeated_string => ['bar1', 'bar2'])
     m2 = proto_module::TestMessage.new(:optional_string => 'foo', :repeated_string => ['bar1', 'bar2'])