diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
new file mode 100644
index 0000000000000000000000000000000000000000..4719890c6b743ade75373ea91fcada000a3342a8
--- /dev/null
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.test.S4;
+
+import org.junit.Test;
+
+import com.oracle.truffle.r.test.TestRBase;
+
+// Checkstyle: stop LineLength
+
+/**
+ * Tests for the S4 object model implementation.
+ */
+public class TestS4 extends TestRBase {
+    @Test
+    public void testSlotAccess() {
+        assertEval("{ `@`(getClass(\"ClassUnionRepresentation\"), virtual) }");
+        assertEval("{ `@`(getClass(\"ClassUnionRepresentation\"), \"virtual\") }");
+        assertEval(Output.IgnoreErrorContext, "{ `@`(getClass(\"ClassUnionRepresentation\"), c(\"virtual\", \"foo\")) }");
+        assertEval("{ getClass(\"ClassUnionRepresentation\")@virtual }");
+        assertEval("{ getClass(\"ClassUnionRepresentation\")@.S3Class }");
+        assertEval("{ c(42)@.Data }");
+        assertEval("{ x<-42; `@`(x, \".Data\") }");
+        assertEval("{ x<-42; `@`(x, .Data) }");
+        assertEval("{ x<-42; slot(x, \".Data\") }");
+        assertEval("{ setClass(\"foo\", contains=\"numeric\"); x<-new(\"foo\"); res<-x@.Data; removeClass(\"foo\"); res }");
+        assertEval("{ setClass(\"foo\", contains=\"numeric\"); x<-new(\"foo\"); res<-slot(x, \".Data\"); removeClass(\"foo\"); res }");
+        assertEval(Output.IgnoreErrorContext, "{ getClass(\"ClassUnionRepresentation\")@foo }");
+        assertEval(Output.IgnoreErrorContext, "{ c(42)@foo }");
+        assertEval(Output.IgnoreErrorContext, " { x<-42; attr(x, \"foo\")<-7; x@foo }");
+        assertEval("{ x<-42; attr(x, \"foo\")<-7; slot(x, \"foo\") }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-c(42); class(x)<-\"bar\"; x@foo }");
+        assertEval("{ x<-getClass(\"ClassUnionRepresentation\"); slot(x, \"virtual\") }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-getClass(\"ClassUnionRepresentation\"); slot(x, virtual) }");
+        assertEval("{ x<-function() 42; attr(x, \"foo\")<-7; y<-asS4(x); y@foo }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-NULL; `@`(x, foo) }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-NULL; x@foo }");
+        assertEval("{ x<-paste0(\".\", \"Data\"); y<-42; slot(y, x) }");
+    }
+
+    @Test
+    public void testSlotUpdate() {
+        assertEval("{ x<-getClass(\"ClassUnionRepresentation\"); x@virtual<-TRUE; x@virtual }");
+        assertEval("{ x<-getClass(\"ClassUnionRepresentation\"); slot(x, \"virtual\", check=TRUE)<-TRUE; x@virtual }");
+        assertEval("{ x<-initialize@valueClass; initialize@valueClass<-\"foo\"; initialize@valueClass<-x }");
+
+        assertEval(Output.IgnoreErrorContext, "{ x<-function() 42; attr(x, \"foo\")<-7; y@foo<-42 }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-function() 42; attr(x, \"foo\")<-7; slot(y, \"foo\")<-42 }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-function() 42; attr(x, \"foo\")<-7; y<-asS4(x); y@foo<-42 }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-NULL; `@<-`(x, foo, \"bar\") }");
+        assertEval(Output.IgnoreErrorContext, "{ x<-NULL; x@foo<-\"bar\" }");
+
+    }
+
+    @Test
+    public void testConversions() {
+        assertEval("{ x<-42; isS4(x) }");
+        assertEval("{ x<-42; y<-asS4(x); isS4(y) }");
+        assertEval("{ isS4(NULL) }");
+        assertEval("{ asS4(NULL); isS4(NULL) }");
+        assertEval("{  asS4(7:42) }");
+    }
+
+    @Test
+    public void testAllocation() {
+        assertEval("{ new(\"numeric\") }");
+        assertEval("{ setClass(\"foo\", representation(j=\"numeric\")); new(\"foo\", j=42) }");
+
+    }
+
+    @Test
+    public void testClassCreation() {
+        // output slightly different from GNU R even though we use R's "show" method to print it
+        assertEval(Ignored.OutputFormatting, "{ setClass(\"foo\", representation(j=\"numeric\")); getClass(\"foo\") }");
+
+        assertEval("{ setClass(\"foo\"); setClass(\"bar\", representation(j = \"numeric\"), contains = \"foo\"); is.null(getClass(\"foo\")@prototype) }");
+    }
+
+    @Test
+    public void testMethods() {
+        // output slightly different from GNU R even though we use R's "show" method to print it
+        assertEval(Ignored.OutputFormatting, "{ setGeneric(\"gen\", function(object) standardGeneric(\"gen\")); res<-print(gen); removeGeneric(\"gen\"); res }");
+        assertEval(Ignored.OutputFormatting, "{ gen<-function(object) 0; setGeneric(\"gen\"); res<-print(gen); removeGeneric(\"gen\"); res }");
+
+        assertEval("{ gen<-function(object) 0; setGeneric(\"gen\"); setClass(\"foo\", representation(d=\"numeric\")); setMethod(\"gen\", signature(object=\"foo\"), function(object) object@d); res<-print(gen(new(\"foo\", d=42))); removeGeneric(\"gen\"); res }");
+
+        assertEval("{ setClass(\"foo\", representation(d=\"numeric\")); setClass(\"bar\",  contains=\"foo\"); setGeneric(\"gen\", function(o) standardGeneric(\"gen\")); setMethod(\"gen\", signature(o=\"foo\"), function(o) \"FOO\"); setMethod(\"gen\", signature(o=\"bar\"), function(o) \"BAR\"); res<-print(c(gen(new(\"foo\", d=7)), gen(new(\"bar\", d=42)))); removeGeneric(\"gen\"); res }");
+
+        assertEval("{ setGeneric(\"gen\", function(o) standardGeneric(\"gen\")); res<-print(setGeneric(\"gen\", function(o) standardGeneric(\"gen\"))); removeGeneric(\"gen\"); res }");
+
+        assertEval("{ setClass(\"foo\"); setMethod(\"diag<-\", \"foo\", function(x, value) 42); removeMethod(\"diag<-\", \"foo\"); removeGeneric(\"diag<-\"); removeClass(\"foo\") }");
+    }
+
+    @Test
+    public void testInternalDispatch() {
+        assertEval("setClass('foo', representation(d='numeric')); setMethod(`$`, signature('foo'), function(x, name) 'FOO'); obj <- new('foo'); obj$asdf");
+
+    }
+
+    @Test
+    public void testStdGeneric() {
+        assertEval("{ standardGeneric(42) }");
+        assertEval("{ standardGeneric(character()) }");
+        assertEval("{ standardGeneric(\"\") }");
+        assertEval("{ standardGeneric(\"foo\", 42) }");
+        assertEval("{ x<-42; class(x)<-character(); standardGeneric(\"foo\", x) }");
+    }
+
+    @Override
+    public String getTestDir() {
+        return "S4";
+    }
+}